npx skills add ...
npx skills add clerk/skills --skill clerk-chrome-extension-patterns
Chrome Extension auth with @clerk/chrome-extension -- popup/sidepanel
npx skills add clerk/skills --skill clerk-chrome-extension-patterns
syncHost to delegate auth to your web appcreateClerkClient() or message passingchrome-extension:// not http:// -- all redirect URLs must use chrome.runtime.getURL('.')key in manifest BEFORE deploying| Method | Popup | Side Panel | syncHost (with web app) |
|---|---|---|---|
| Email + OTP | Yes | Yes | Yes |
| Email + Link | No | No | Yes |
| Email + Password | Yes | Yes | Yes |
| Username + Password | Yes | Yes | Yes |
| SMS + OTP | Yes | Yes | Yes |
| OAuth (Google, GitHub, etc.) | NO | NO | YES |
| SAML | NO | NO | YES |
| Passkeys | Yes | Yes | Yes |
| Google One Tap | No | No | Yes |
| Web3 | No | No | Yes |
Enable Native API in Clerk Dashboard under Native applications. Required for all extension integrations.
.env.development:
src/popup.tsx:
Use mode="modal" for SignInButton -- navigating to a separate page breaks the popup flow.
Use this when you need OAuth, SAML, or want the extension to reflect sign-in from your web app.
How it works: The extension reads the Clerk session cookie from your web app's domain via host_permissions.
Step 1 -- Environment variables:
.env.development:
.env.production:
Step 2 -- Add syncHost prop:
Step 3 -- Configure host_permissions in package.json:
Step 4 -- Add extension ID to web app's allowed origins via Clerk API:
Hide unsupported auth methods in popup when using syncHost:
Full guide: references/sync-host.md
Import from @clerk/chrome-extension/client (not @clerk/chrome-extension).
Background service worker (src/background/index.ts):
The background: true flag keeps sessions fresh even when popup/sidepanel is closed. Without it, tokens expire after 60 seconds.
Popup with vanilla JS (src/popup.ts):
Full guide: references/create-clerk-client.md
For extensions that run entirely in the background and sync with a web app.
Uses syncHost + createClerkClient with background: true to read auth state from the web app's cookies.
Requires host_permissions for the sync host domain in package.json.
Full guide: references/headless-extension.md
Content scripts run in an isolated JavaScript world injected into web pages. Clerk cannot be used directly -- origin restrictions prevent it.
Use message passing to request auth state from the background service worker:
Full guide: references/content-scripts.md
Without a pinned key, Chrome derives the CRX ID from a random key at build time. This rotates every rebuild, breaking allowed origins.
Option A -- Plasmo Itero (recommended):
Option B -- OpenSSL:
.env.chrome:
package.json:
Add chrome-extension://YOUR_STABLE_CRX_ID to Clerk Dashboard > Allowed Origins.
| Storage type | Scope | Clears on |
|---|---|---|
chrome.storage.local | Device | Uninstall or manual clear |
chrome.storage.session | Session | Browser close |
chrome.storage.sync | All devices | Uninstall (size-limited, 8KB) |
localStorage | Popup only | Popup close -- do not use for auth |
| Symptom | Cause | Fix |
|---|---|---|
| Redirect loop on sign-in | Missing CRX URL in ClerkProvider props | Set afterSignOutUrl, signInFallbackRedirectUrl |
| OAuth button not working | OAuth not supported in popup | Use syncHost to delegate to web app |
| Auth state stale after web app sign-in | syncHost not configured | Add syncHost prop + host_permissions |
| Side panel shows signed-out after web sign-in | Known limitation | User must close and reopen the side panel |
| Background can't get token after 60s | Session expired, no background refresh | Use createClerkClient({ background: true }) |
| Content script can't access Clerk | Isolated world + origin restrictions | Use message passing to background service worker |
| Auth breaks after rebuild | CRX ID rotated | Configure stable key via .env.chrome |
PLASMO_PUBLIC_ var undefined | Wrong env file | Use .env.development, not .env |
| Bot protection errors | Cloudflare not supported in extensions | Disable bot protection in Clerk Dashboard |
| Token cache not persisting | Using localStorage in popup | Use chrome.storage.local or pass tokenCache prop |
| Feature | Plan |
|---|---|
| Basic popup auth (email/password, OTP) | Free |
| Passkeys | Free |
| syncHost | Requires Pro (custom domain) |
| OAuth through syncHost | Pro + OAuth configured on web app |
| SAML through syncHost | Enterprise |
| Bot protection | N/A -- must be disabled for extensions |
clerk-setup - Initial Clerk installclerk-custom-ui - Custom flows & appearance