Capacitor: fetch versus CapacitorHttp for network requests
Decide when to use CapacitorHttp instead of WebView fetch, with CORS, cookie, and testing trade-offs.
09 Feb 2026, 12:19 UTC

The decision and your constraints
Problem: a network request works in your browser build but fails on iOS or Android with a CORS error, or you are unsure whether to standardize on one HTTP implementation across web and native targets.
Useful takeaway: keep standard fetch for same-origin requests and browser-first code paths. Switch to CapacitorHttp.request() only for cross-origin native calls that fail under WebView CORS, and verify cookie continuity because the native client and the WebView may not share the same cookie store.
A WebView is the embedded browser view that renders your web code on iOS and Android. CORS is Cross-Origin Resource Sharing, a browser security feature that blocks web code from reading responses from a different origin unless the server explicitly allows it. CapacitorHttp is Capacitor's plugin for making HTTP requests through the native networking stack instead of the WebView.
Constraints to list before choosing:
- Target platforms: iOS, Android, web/PWA, or all three.
- Whether the API is same-origin or cross-origin relative to the WebView origin.
- Whether login state depends on cookies shared between WebView and native code.
- Whether you need upload progress, streaming bodies, or request cancellation.
- How you plan to mock network calls in automated tests.
Version assumption: verify your installed @capacitor/core version in package.json. CapacitorHttp options and type definitions change between major releases, so check node_modules/@capacitor/core before relying on a specific property.
Option comparison
| Option | Runs on | CORS behavior | Cookie handling | Cancellation | Best for |
|---|---|---|---|---|---|
Standard fetch | WebView and browser | Enforced by WebView | WebView cookie jar | AbortController | Same-origin calls, browser-first code, simple mocking |
CapacitorHttp.request() | Native iOS and Android; web falls back to fetch | Bypassed on native stack | Native HTTP client cookie store | Plugin-specific, not identical to AbortSignal | Cross-origin native API calls blocked by CORS |
Trade-offs to accept
CapacitorHttp executes requests on the native networking stack (NSURLSession on iOS, OkHttp on Android), so it bypasses WebView CORS and origin restrictions. That is its main advantage. The cost is a different request signature and cancellation model than fetch.
On the browser or PWA target, CapacitorHttp falls back to fetch, so one code path can target web and native. Native-only configuration, such as certificate pinning through native project settings, does not apply on the web.
Cookie handling is a key differentiator. CapacitorHttp manages cookies through the native HTTP client, which may not automatically share state with the WebView cookie jar. If your login flow sets a cookie in the WebView and your API call uses CapacitorHttp, the request may arrive without the session cookie, causing intermittent auth failures on real devices.
CapacitorHttp supports common HTTP methods and configurable response types, but advanced WebView capabilities such as streaming uploads or real-time progress events are not part of its core API. Response types like blob and arraybuffer, and large payloads, behave differently across the native bridge; test file downloads explicitly on each platform.
Testing and mocking are simpler with fetch using tools such as jest-fetch-mock or MSW. CapacitorHttp requires mocking the plugin module or running in a native test harness, which increases test complexity.
Implementation example
This wrapper prefers fetch by default and uses CapacitorHttp only when you explicitly opt in on a native platform.
import { CapacitorHttp } from '@capacitor/core';
async function request(url: string, init: RequestInit = {}) {
const isNative = window.capacitor !== undefined;
const forceNative = init.forceNative === true;
if (isNative && forceNative) {
const response = await CapacitorHttp.request({
url,
method: init.method ?? 'GET',
headers: init.headers as Record<string, string>,
data: init.body,
});
return {
ok: response.status >= 200 && response.status < 300,
status: response.status,
headers: response.headers,
json: async () => response.data,
};
}
return fetch(url, init);
}
// Usage
const result = await request('https://api.example.com/data', { forceNative: true });
if (!result.ok) throw new Error(`HTTP ${result.status}`);
const payload = await result.json();
Replace https://api.example.com/data with your API endpoint. The forceNative flag is a convention in this example, not a Capacitor API; adapt it to your platform detection logic.
Capacitor's server.iosScheme and androidScheme settings affect the WebView origin and therefore whether fetch hits CORS. Example configuration:
server: {
iosScheme: 'https',
androidScheme: 'https',
hostname: 'localhost',
}
Changing these schemes can break existing native HTTP assumptions, so review current values before editing.
Validation steps
- Run the app on an iOS simulator and Android emulator against a CORS-restricted API using both
fetchandCapacitorHttp. Confirm the native path succeeds where the WebView path fails. - Inspect native cookie stores after login, using Safari Web Inspector for iOS or Chrome DevTools remote debugging for Android, to confirm whether
CapacitorHttpand WebViewfetchshare session cookies. - Execute the same request in a browser with
npm run devorvite previewto confirm theCapacitorHttpweb fallback behaves like standardfetch. - Check the installed
@capacitor/coreversion and its type definitions innode_modulesto confirm supported options before relying on them.
Command context: run npm install and npx cap sync in your project root. No special operating-system permissions are required, but npx cap sync modifies native project files; commit or back up the native directories first. Expected check: rebuild in Xcode or Android Studio, run on a simulator or emulator, and observe that the cross-origin request returns a status instead of a CORS error. Risk: mixing fetch and CapacitorHttp can cause intermittent auth failures if cookie stores do not share state.
Limitations and rollback
Do not assume CapacitorHttp supports every fetch feature identically across platforms. Streaming bodies, FormData progress, and AbortSignal behavior may differ. HTTP error statuses may not reject the way fetch does, so centralize response validation in a shared wrapper.
Practical way to check the result: after deploying the wrapper, make a cross-origin request on a physical device and confirm the response status and body. Then log in through the WebView and repeat the request through CapacitorHttp; if the session cookie is missing, you have confirmed a cookie-store separation issue.
Rollback: replace the CapacitorHttp branch in the wrapper with fetch and redeploy. If you changed capacitor.config.ts schemes, revert them and run npx cap sync again. No database migration is required.
This draft requires human review before publication because the research brief flags current verification requirements. Verify API surface and behavior against your installed Capacitor version.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.