Use Capacitor HTTP for native API calls in Ionic, fetch for web
Route Ionic API calls through Capacitor HTTP on native devices while keeping fetch for browser and PWA builds.
23 Jan 2026, 02:19 UTC

The problem: your API works in the browser but fails on a device
An Ionic app often runs fine during ionic serve, then starts failing after you build for iOS or Android. Requests that include custom headers, non-simple content types, or authentication cookies can trigger CORS preflight checks. On a device, the app runs inside a WebView—a browser engine embedded in the native wrapper—so fetch is still bound by the browser's same-origin policy. The server may reject the request or never see it because the browser blocks the preflight response.
The useful takeaway: you do not have to choose between a native build and a web build. Keep one API function and route it through Capacitor's HTTP plugin on native platforms, while falling back to fetch for the browser and PWA. The plugin sends requests from the native networking stack, so it does not trigger WebView CORS preflights.
How the two network paths differ
fetch executes in the WebView. The browser adds an Origin header, enforces CORS, and may require Access-Control-Allow-Origin and Access-Control-Allow-Headers from the server. Capacitor HTTP executes the request in native code on iOS or Android. The native stack does not apply WebView CORS rules, which removes the preflight problem for native builds.
This is not a security bypass. The server still controls who can call its endpoints, and TLS certificate validation still happens. It only changes where the request originates from inside your app.
A platform-aware request wrapper
Create a single api() function so the rest of your app does not need to know which transport is running. Option names can change between major Capacitor versions, so check your installed @capacitor/http package before copying this verbatim.
import { Capacitor } from '@capacitor/core';
import { CapacitorHttp } from '@capacitor/http';
const API_BASE = 'https://api.example.com';
async function api(path: string, init: RequestInit = {}): Promise {
const url = API_BASE + path;
const headers = init.headers ?? {};
if (Capacitor.isNativePlatform()) {
const response = await CapacitorHttp.request({
url,
method: init.method ?? 'GET',
headers,
data: init.body,
responseType: 'json',
});
if (response.status >= 400) {
throw new Error(`API ${response.status}`);
}
return response.data;
}
const response = await fetch(url, {
...init,
headers,
});
if (!response.ok) {
throw new Error(`API ${response.status}`);
}
return response.json();
}
Call it the same way everywhere:
const user = await api('/users/me', {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
Cookies, auth, and what does not carry over automatically
Because native HTTP bypasses the WebView, cookies stored by the browser are not automatically sent with CapacitorHttp.request. If your session relies on a cookie set during a WebView login, the native request may arrive unauthenticated. Prefer bearer tokens in an Authorization header, or explicitly sync cookies through the plugin's cookie methods if your Capacitor version supports them.
Streaming very large responses is also awkward with a buffered request. For large uploads or downloads, use the plugin's dedicated file transfer methods or keep fetch for that specific flow.
Add it to your project and verify
Run these commands in your Ionic project root. You need normal file-system permissions; no sudo is required.
npm install @capacitor/core @capacitor/http
npx cap sync
Replace the package names with the versions already in your project if you are pinning dependencies. After installing, rebuild the native project before testing on a simulator or device.
Verification steps:
- Call the same endpoint from a browser build and confirm
fetchstill works with your server's CORS headers. - Run the native build on a device or simulator and confirm the request completes without a CORS preflight error.
- Inspect server logs for the
Originheader. Native requests should not include the WebView origin; browser requests should. - Test an authenticated call and confirm whether the session cookie is sent. If it is not, switch to bearer tokens or explicitly manage cookies.
- Exercise a 4xx or 5xx response and confirm your wrapper throws a normalized error instead of leaking raw response details.
Do not disable certificate validation to make self-signed certificates work during development. That weakens TLS for the native stack and is easy to forget before shipping.
Limitation: the web build still needs CORS
Your PWA and browser users still go through fetch, so the server must continue sending valid CORS responses for those clients. Native HTTP only removes the friction for iOS and Android builds. Pin your Capacitor versions and re-check CapacitorHttp.request options after an upgrade, because method names and response shapes are version-sensitive.
Closing
Start with one wrapper function, route native platforms through Capacitor HTTP, and keep fetch as the web fallback. Test the same endpoint on a device and in a browser before you treat the issue as resolved.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.