GuidesCAPACITOR

Capacitor and Ionic CORS errors

A Capacitor app's web code runs in a WebView with the origin capacitor://localhost on iOS and https://localhost on Android (Capacitor 6 and later), so an API without CORS headers fails on a device exactly as in a browser. The native fix is CapacitorHttp, which moves fetch and XMLHttpRequest onto native networking, but it is off by default and web or PWA builds still need CORS. https://proxy.cors.dev/ answered all 5 app and dev origins tested for GET with no signup (about 600 requests a minute per IP), and cors.dev Pro ($5 a month for 500,000 requests) accepts capacitor://localhost and ionic://localhost as registered origins for POST and auth headers.

Why a Capacitor app hits CORS

A Capacitor app renders its web code in a native WebView that serves your files from a local origin. Capacitor's configuration defaults are server.hostname localhost, server.iosScheme capacitor and server.androidScheme https, so the app's origin is capacitor://localhost on iOS and https://localhost on Android.

Capacitor 6 made https the default androidScheme for existing apps; on Capacitor 5 and earlier the Android origin was http://localhost unless configured. Capacitor's docs say iosScheme cannot be set to a scheme that WKWebView already handles, such as http or https.

Ionic's CORS guide lists two more origins: ionic://localhost for Cordova apps on iOS using cordova-plugin-ionic-webview 3.x, and http://localhost:8100 for ionic serve. Its table still shows http://localhost for Capacitor on Android, the default before Capacitor 6.

fetch() runs inside the WebView, so an API that sends no Access-Control-Allow-Origin for these origins fails on a device the same way it fails in a desktop browser.

App and dev-server origins, checked October 2026
Where the app runsOriginSet byproxy.cors.dev free answers
Capacitor on iOScapacitor://localhostserver.iosScheme and server.hostname*
Capacitor 6 or later on Androidhttps://localhostserver.androidScheme and server.hostnameEchoes the origin
Capacitor 5 or earlier on Androidhttp://localhostserver.androidSchemeEchoes the origin
Cordova with ionic-webview 3.x on iOSionic://localhostPlugin default*
ionic servehttp://localhost:8100Dev serverEchoes the origin

Option 1: CapacitorHttp native requests

CapacitorHttp is bundled with @capacitor/core. Capacitor's docs: it "provides native http support via patching fetch and XMLHttpRequest to use native libraries". Native requests are not subject to CORS. The patch is off by default; set plugins.CapacitorHttp.enabled to true in capacitor.config.ts.

capacitor.config.ts: turn on CapacitorHttp
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.news',
  appName: 'News',
  webDir: 'dist',
  plugins: {
    CapacitorHttp: {
      enabled: true,
    },
  },
};

export default config;

The trade-offs, from the Capacitor and Ionic docs:

  • Whole app: The patch replaces fetch and XMLHttpRequest for every request in the app, including those from third-party libraries.
  • Web builds: On the web it falls back to the browser's own fetch, so a PWA or ionic serve still needs CORS. Ionic's guide says the same for any web context.
  • Request bodies: When you call CapacitorHttp.request() directly, data takes only a string or JSON on native.
  • Large files: Transfers belong in @capacitor/file-transfer with @capacitor/filesystem.

server.url is not a fix either. It points the WebView at a remote URL for live reload, and Capacitor's docs say it is not intended for use in production.

Option 2: one proxy for every platform

The BBC World RSS feed (https://feeds.bbci.co.uk/news/world/rss.xml, 21,220 bytes, text/xml) sends no Access-Control-Allow-Origin. On 28 October 2026 it answered 200 through https://proxy.cors.dev/ for every origin we sent: Access-Control-Allow-Origin: * for capacitor://localhost and ionic://localhost, and the exact origin echoed for https://localhost, http://localhost and http://localhost:8100.

The proxy works with or without CapacitorHttp, and the same code runs on iOS, Android, ionic serve and a deployed PWA. With CapacitorHttp on, native builds no longer need the proxy, but the web build still does. The snippet returns 10 headlines parsed with DOMParser.

headlines.ts: one fetch for iOS, Android and the web
const FEED = 'https://feeds.bbci.co.uk/news/world/rss.xml';

export async function loadHeadlines() {
  const response = await fetch('https://proxy.cors.dev/' + FEED);
  if (!response.ok) {
    throw new Error(response.headers.get('X-Cors-Error') ?? `HTTP ${response.status}`);
  }

  const xml = new DOMParser().parseFromString(await response.text(), 'text/xml');
  return [...xml.querySelectorAll('item')].slice(0, 10).map((item) => ({
    title: item.querySelector('title')?.textContent ?? '',
    link: item.querySelector('link')?.textContent ?? '',
    published: item.querySelector('pubDate')?.textContent ?? '',
  }));
}

The free tier needs no signup and no key. The proxy answers the OPTIONS preflight itself with 204, and when it refuses a request, the X-Cors-Error response header names the reason.

Origins to register for cors.dev Pro

POST, PUT, PATCH and DELETE, request bodies up to 1 MiB, and Authorization or custom headers need cors.dev Pro. A Connection accepts app origins as well as web origins.

  1. Start the trial

    Sign up with Google. The 7-day trial includes 1,000 requests and needs no card.

  2. List your origins

    capacitor://localhost, https://localhost and ionic://localhost as your builds need, plus http://localhost:8100 for ionic serve and your PWA's https origin, up to 10 in total.

  3. List the hosts and send the key

    Enable up to 20 target hosts and send the publishable key in an X-Cors-Key header.

Origin: null never matches a Connection, and an unlisted origin answers 403 origin_not_allowed. Every Capacitor app on iOS shares capacitor://localhost, and every Capacitor 6 or later app on Android shares https://localhost, so an origin check cannot tell your app from someone else's. The list of target hosts is what limits the key. Keep secret API keys on a server: anyone can unpack an app and read the key.

Limits on each tier

The free tier allows about 600 requests per minute per IP and answers 429 with a Retry-After header that JavaScript can read; there is no monthly quota. It refuses Authorization, and header names containing token, apikey, api-key, api_key, secret or password or ending in -key, with 400 header_not_allowed, and drops other custom headers, including names containing csrf or xsrf, listing them in X-Cors-Dropped-Headers.

cors.dev tiers for Capacitor apps, checked October 2026
LimitFree, no keyPro, $5 a month
PriceFree, no signup$5 a month, 7-day trial with 1,000 requests
MethodsGET, HEADGET, HEAD, POST, PUT, PATCH, DELETE
Monthly requestsNo quota500,000
ThroughputAbout 600 a minute per IP3,000 a minute per account, 100 in flight
Request headersAccept, Accept-Language, If-None-Match, If-Modified-Since, RangeAdds Authorization, Content-Type, configured custom headers
Page originsAnyUp to 10 exact origins per Connection
Response size and deadline6 MiB, 30 secondsSame as free
CachingNoneGET up to 300 seconds, when the upstream allows it

Good questions.

Why does my Ionic app work in ionic serve but fail on an iPhone?

The origin changes. ionic serve runs in a desktop browser on http://localhost:8100, while the iPhone build sends capacitor://localhost, or ionic://localhost on Cordova with cordova-plugin-ionic-webview 3.x. An API that allows the first origin but not the second blocks the device build.

Did Capacitor 6 change the Android origin?

Yes. Capacitor 6 made https the default server.androidScheme for existing apps, so the default Android origin is https://localhost. On Capacitor 5 and earlier it was http://localhost unless you configured it, which is the value Ionic's CORS table still shows.

Can server.url fix CORS in a production app?

No. server.url loads a remote URL in the WebView for live reload, and Capacitor's docs say it is not intended for use in production. Fix CORS with CapacitorHttp, an API that sends the right headers, or a proxy.

Does CapacitorHttp work in a PWA build?

Not as a CORS fix. On the web, CapacitorHttp uses the browser's own fetch, so a PWA build still needs CORS, as Ionic's guide notes. A proxy prefix such as https://proxy.cors.dev/ keeps one fetch() call working across iOS, Android and the PWA.

Is it safe to register capacitor://localhost on a Connection?

It works, but it does not identify your app: every Capacitor iOS app sends capacitor://localhost, and the X-Cors-Key inside the app is readable by anyone who unpacks it. On cors.dev Pro, the Connection's list of up to 20 target hosts restricts what that key can reach. Secret API keys belong on your own server.