Summary
Clerk.navigate() validates the protocol of every internal navigation against a hardcoded allow-list that includes wails: and chrome-extension: but not capacitor:. As a result, Clerk is unusable in a Capacitor iOS app: every relative navigation is rewritten to /, which breaks sign-in.
The code
From the shipped clerk.browser.js v5 (module 55809):
let r = ["http:", "https:", "wails:", "chrome-extension:"];
and in Clerk.navigate:
let i = new URL(e, window.location.href);
this.#N.includes(i.protocol) || (
console.warn(`Clerk: "${i.protocol}" is not a valid protocol. Redirecting to "/" instead. ...`),
i = new URL("/", window.location.href)
);
Why this breaks Capacitor specifically
Capacitor's iOS WebView serves bundled content from a custom scheme — capacitor://localhost by default. iosScheme cannot be set to http/https, because WKWebView reserves them (Capacitor docs, and Apple's setURLSchemeHandler documentation).
Because the target is resolved with new URL(e, window.location.href), every relative navigation on that origin returns protocol capacitor:, fails the check, and is rewritten to /.
Observed on device:
⚡️ [warn] - Clerk: "capacitor:" is not a valid protocol. Redirecting to "/" instead.
fired twice — once during initialisation and once mid sign-in. User-visible symptom: the email verification screen appears and is immediately replaced by the sign-up route, so sign-in can never complete.
Not fixable by configuration
signInUrl, signUpUrl, forceRedirectUrl, fallbackRedirectUrl etc. all supply relative paths, which is exactly what resolves to the rejected protocol. Supplying absolute https:// URLs passes the check but navigates the WebView out of the app, which is worse.
Proposed fix
Add "capacitor:" to the list. Clerk's docs already name Capacitor.js as a supported browser-like stack for allowed_origins, and wails: / chrome-extension: establish the precedent for embedded-shell schemes.
- let r = ["http:", "https:", "wails:", "chrome-extension:"];
+ let r = ["http:", "https:", "wails:", "chrome-extension:", "capacitor:"];
Ideally the list would also be extensible via ClerkOptions, so custom iosScheme values (Capacitor allows any non-reserved scheme) are supported without another release.
Current workaround
Setting iosScheme: 'wails' in capacitor.config.ts, so the origin becomes wails://localhost and satisfies the existing list. It works, but borrowing another framework's scheme name is obviously not intended usage.
Environment
@clerk/clerk-react 5.35.3, @clerk/types 4.68.0, clerk-js v5 (hot-loaded)
- Capacitor 8, iOS
Summary
Clerk.navigate()validates the protocol of every internal navigation against a hardcoded allow-list that includeswails:andchrome-extension:but notcapacitor:. As a result, Clerk is unusable in a Capacitor iOS app: every relative navigation is rewritten to/, which breaks sign-in.The code
From the shipped
clerk.browser.jsv5 (module55809):and in
Clerk.navigate:Why this breaks Capacitor specifically
Capacitor's iOS WebView serves bundled content from a custom scheme —
capacitor://localhostby default.iosSchemecannot be set tohttp/https, because WKWebView reserves them (Capacitor docs, and Apple'ssetURLSchemeHandlerdocumentation).Because the target is resolved with
new URL(e, window.location.href), every relative navigation on that origin returns protocolcapacitor:, fails the check, and is rewritten to/.Observed on device:
fired twice — once during initialisation and once mid sign-in. User-visible symptom: the email verification screen appears and is immediately replaced by the sign-up route, so sign-in can never complete.
Not fixable by configuration
signInUrl,signUpUrl,forceRedirectUrl,fallbackRedirectUrletc. all supply relative paths, which is exactly what resolves to the rejected protocol. Supplying absolutehttps://URLs passes the check but navigates the WebView out of the app, which is worse.Proposed fix
Add
"capacitor:"to the list. Clerk's docs already name Capacitor.js as a supported browser-like stack forallowed_origins, andwails:/chrome-extension:establish the precedent for embedded-shell schemes.Ideally the list would also be extensible via
ClerkOptions, so customiosSchemevalues (Capacitor allows any non-reserved scheme) are supported without another release.Current workaround
Setting
iosScheme: 'wails'incapacitor.config.ts, so the origin becomeswails://localhostand satisfies the existing list. It works, but borrowing another framework's scheme name is obviously not intended usage.Environment
@clerk/clerk-react5.35.3,@clerk/types4.68.0, clerk-js v5 (hot-loaded)