clerk-chrome-extension-patterns
This skill provides patterns for implementing Clerk authentication in Chrome extensions using Plasmo, covering popup and side panel UI setup, service worker…
Identity proven by GitHub's verified organisation badge (clerk)
- Agents
- Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, claude.ai and API
- License
- MIT (permissive)
- Pin
- a02dbd2pinned Oct 9, 2026
- Context cost
- about 2,700 tokenswhen the skill loads
- GitHub stars
- 86on clerk/skills
- Scripts
- None found
What it does
“Chrome Extension auth with @clerk/chrome-extension -- popup/sidepanel setup, syncHost for OAuth/SAML via web app, createClerkClient for service workers and headless extensions, stable CRX ID. Triggers on: Chrome extension auth, Plasmo clerk, popup sign-in, syncHost, background service worker token, createClerkClient, headless extension.”
Generated summary from the skill's own text
This skill provides patterns for implementing Clerk authentication in Chrome extensions using Plasmo, covering popup and side panel UI setup, service worker token management with createClerkClient, and syncHost for delegating OAuth and SAML flows to a companion web app. It includes environment configuration, manifest setup for stable CRX IDs, and message-passing patterns for content scripts.
When to use it
- Building Chrome extensions with email, password, OTP, or passkey sign-in flows
- Integrating OAuth or SAML by syncing auth state with a web app domain
- Managing auth tokens in background service workers without React hooks
- Setting up headless extensions that run entirely in the background with web app session sync
When not to
- Extensions needing OAuth or SAML directly in popups without a companion web app
- Projects not using Plasmo or requiring alternative extension build tools
- Extending Clerk to environments where bot protection cannot be disabled
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add clerk/skills --skill clerk-chrome-extension-patternsInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install clerk/skills clerk-chrome-extension-patterns@a02dbd2a933b8929525129a6cb9dcf6ed66d0adfInstalls the commit we checked (a02dbd2).
Copy by hand
Download the folder at a02dbd2 and put it in .claude/skills/clerk-chrome-extension-patterns for Claude Code and in .agents/skills/clerk-chrome-extension-patterns for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "clerk-chrome-extension-patterns" by Clerk. Source, pinned to commit a02dbd2a933b8929525129a6cb9dcf6ed66d0adf: https://github.com/clerk/skills/tree/a02dbd2a933b8929525129a6cb9dcf6ed66d0adf/skills/clerk-chrome-extension-patterns 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/clerk-chrome-extension-patterns/ for Claude Code and .agents/skills/clerk-chrome-extension-patterns/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/clerk/clerk-chrome-extension-patterns
SKILL.md at the pin
---
name: clerk-chrome-extension-patterns
description: 'Chrome Extension auth with @clerk/chrome-extension -- popup/sidepanel
setup, syncHost for OAuth/SAML via web app, createClerkClient for service workers
and headless extensions, stable CRX ID. Triggers on: Chrome extension auth, Plasmo
clerk, popup sign-in, syncHost, background service worker token, createClerkClient,
headless extension.'
license: MIT
allowed-tools: WebFetch
compatibility: Requires PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY (Plasmo prefix for public env vars) and CLERK_FRONTEND_API.
metadata:
author: clerk
version: 2.0.0
references:
- references/sync-host.md
- references/create-clerk-client.md
- references/content-scripts.md
- references/headless-extension.md
---
# Chrome Extension Patterns
## CRITICAL RULES
1. OAuth (Google, GitHub, etc.) and SAML are NOT supported in popups or side panels -- use `syncHost` to delegate auth to your web app
2. Email links (magic links) don't work in popups -- the popup closes when the user clicks outside, resetting sign-in state
3. Side panels don't auto-refresh auth state -- users must close and reopen the side panel after signing in via the web app
4. Service workers and content scripts have NO access to Clerk React hooks -- use `createClerkClient()` or message passing
5. Extension URLs use `chrome-extension://` not `http://` -- all redirect URLs must use `chrome.runtime.getURL('.')`
6. Without a stable CRX ID, every rebuild breaks auth -- configure `key` in manifest BEFORE deploying
7. Content scripts cannot use Clerk directly due to origin restrictions -- Clerk enforces strict allowed origins
8. Bot protection must be DISABLED in Clerk Dashboard -- Cloudflare bot detection is not supported in extension environments
## Authentication Options
| 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 |
## Quick Start (Plasmo)
```bash
npx create-plasmo --with-tailwindcss --with-src my-extension
cd my-extension
npm install @clerk/chrome-extension
```
Enable **Native API** in Clerk Dashboard under Native applications. Required for all extension integrations.
`.env.development`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
```
`src/popup.tsx`:
```tsx
import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from '@clerk/chrome-extension'
const PUBLISHABLE_KEY = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const EXTENSION_URL = chrome.runtime.getURL('.')
if (!PUBLISHABLE_KEY) {
throw new Error('Missing PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY')
}
function IndexPopup() {
return (
<ClerkProvider
publishableKey={PUBLISHABLE_KEY}
afterSignOutUrl={`${EXTENSION_URL}/popup.html`}
signInFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
signUpFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
>
<Show when="signed-out">
<SignInButton mode="modal" />
<SignUpButton mode="modal" />
</Show>
<Show when="signed-in">
<UserButton />
</Show>
</ClerkProvider>
)
}
export default IndexPopup
```
Use `mode="modal"` for `SignInButton` -- navigating to a separate page breaks the popup flow.
## syncHost -- Sync Auth with Web App
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`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
PLASMO_PUBLIC_CLERK_SYNC_HOST=http://localhost
```
`.env.production`:
```
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_FRONTEND_API=https://clerk.your-domain.com
PLASMO_PUBLIC_CLERK_SYNC_HOST=https://clerk.your-domain.com
```
**Step 2 -- Add `syncHost` prop:**
```tsx
const SYNC_HOST = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST
<ClerkProvider
publishableKey={PUBLISHABLE_KEY}
syncHost={SYNC_HOST}
afterSignOutUrl="/"
routerPush={(to) => navigate(to)}
routerReplace={(to) => navigate(to, { replace: true })}
>
```
**Step 3 -- Configure `host_permissions` in `package.json`:**
```json
{
"manifest": {
"key": "$CRX_PUBLIC_KEY",
"permissions": ["cookies", "storage"],
"host_permissions": [
"$PLASMO_PUBLIC_CLERK_SYNC_HOST/*",
"$CLERK_FRONTEND_API/*"
]
}
}
```
**Step 4 -- Add extension ID to web app's allowed origins via Clerk API:**
```bash
curl -X PATCH https://api.clerk.com/v1/instance \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-type: application/json" \
-d '{"allowed_origins": ["chrome-extension://YOUR_EXTENSION_ID"]}'
```
**Hide unsupported auth methods in popup when using syncHost:**
```tsx
<SignIn
appearance={{
elements: {
socialButtonsRoot: 'plasmo-hidden',
dividerRow: 'plasmo-hidden',
},
}}
/>
```
Full guide: `references/sync-host.md`
## createClerkClient() for Vanilla JS / Service Workers
Import from `@clerk/chrome-extension/client` (not `@clerk/chrome-extension`).
**Background service worker** (`src/background/index.ts`):
```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'
const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
async function getToken(): Promise<string | null> {
const clerk = await createClerkClient({
publishableKey,
background: true,
})
if (!clerk.session) return null
return await clerk.session.getToken()
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
getToken()
.then((token) => sendResponse({ token }))
.catch((error) => {
console.error('[Background] Error:', JSON.stringify(error))
sendResponse({ token: null })
})
return true
})
```
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`):
```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'
const EXTENSION_URL = chrome.runtime.getURL('.')
const POPUP_URL = `${EXTENSION_URL}popup.html`
const clerk = createClerkClient({ publishableKey })
clerk.load({
afterSignOutUrl: POPUP_URL,
signInForceRedirectUrl: POPUP_URL,
signUpForceRedirectUrl: POPUP_URL,
allowedRedirectProtocols: ['chrome-extension:'],
}).then(() => {
clerk.addListener(render)
render()
})
```
Full guide: `references/create-clerk-client.md`
## Headless Extension (no popup, no side panel)
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.
```typescript
import { createClerkClient } from '@clerk/chrome-extension/client'
const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const syncHost = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST
async function getAuthenticatedUser() {
const clerk = await createClerkClient({
publishableKey,
syncHost,
background: true,
})
return clerk.user
}
```
Requires `host_permissions` for the sync host domain in `package.json`.
Full guide: `references/headless-extension.md`
## Content Scripts
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:
```typescript
// content.ts
async function getToken(): Promise<string | null> {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ type: 'GET_TOKEN' }, (response) => {
resolve(response?.token ?? null)
})
})
}
async function main() {
const token = await getToken()
if (!token) return
// use token for authenticated API calls
}
main()
```
Full guide: `references/content-scripts.md`
## Stable CRX ID
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):**
1. Visit [Plasmo Itero Generate Keypairs](https://itero.plasmo.com/ext/generate-keypairs)
2. Click "Generate KeyPairs" -- save Private Key securely, copy Public Key and CRX ID
**Option B -- OpenSSL:**
```bash
openssl genrsa -out key.pem 2048
# Use Plasmo Itero to convert or extract the public key in correct format
```
**`.env.chrome`:**
```
CRX_PUBLIC_KEY="<PUBLIC KEY from Itero>"
```
**`package.json`:**
```json
{
"manifest": {
"key": "$CRX_PUBLIC_KEY",
"permissions": ["cookies", "storage"],
"host_permissions": [
"http://localhost/*",
"$CLERK_FRONTEND_API/*"
]
}
}
```
Add `chrome-extension://YOUR_STABLE_CRX_ID` to Clerk Dashboard > Allowed Origins.
## Token Cache (persist across popup closes)
```tsx
const tokenCache = {
async getToken(key: string) {
const result = await chrome.storage.local.get(key)
return result[key] ?? null
},
async saveToken(key: string, token: string) {
await chrome.storage.local.set({ [key]: token })
},
async clearToken(key: string) {
await chrome.storage.local.remove(key)
},
}
<ClerkProvider publishableKey={PUBLISHABLE_KEY} tokenCache={tokenCache}>
```
| 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 |
## Common Pitfalls
| 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 |
## Plan Requirements
| 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 |
## See Also
- `clerk-setup` - Initial Clerk install
- `clerk-custom-ui` - Custom flows & appearance
From clerk/skills at a02dbd2, shown as plain text under its license: MIT (permissive). The license text is in skills/clerk-chrome-extension-patterns/SKILL.md.
View SKILL.md on GitHub at a02dbd2Files
- SKILL.md12 KB · 364ccf66dd86
- evals/
- evals.json4.9 KB · 0c6e4215bc0f
- references/
- content-scripts.md3.2 KB · 9aab31092a40
- create-clerk-client.md4.0 KB · 3c5aee8f5ef0
- headless-extension.md3.8 KB · 170cf255544d
- sync-host.md4.2 KB · 3da8455e4ea7
- templates/
- chrome-ext-basic-auth/
- package.json450 B · 4664e6650d62
- src/
- popup.tsx1.1 KB · c6ac9597ead1
- tsconfig.json163 B · e8c1f3abe503
9 files, 34 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at a02dbd2More from Clerk
audit-clerk-skill
Audits the bundled clerk-cli skill against the Clerk CLI source repository, comparing commands, flags, and agent-mode behavior to detect drift, gaps, and…
Claude Code · about 1.9K tokens · pinned 6 days ago
audit-expo-skill
Audits the clerk-expo skill against the @clerk/expo SDK source and clerk-docs to detect API drift, missing coverage, and stale claims.
Claude Code · about 2.2K tokens · pinned 6 days ago
clerk
This skill routes Clerk authentication tasks to specialized sub-skills based on the user's specific need, such as framework setup, CLI operations, custom UI…
Claude Code, Codex, Cursor +4 · about 1.4K tokens · pinned 6 days ago
clerk-android
This skill implements Clerk authentication in native Android apps written in Kotlin and Jetpack Compose by following the clerk-android SDK patterns and…
Claude Code, Codex, Cursor +4 · about 2K tokens · pinned 6 days ago
clerk-astro-patterns
This skill teaches the agent to integrate Clerk authentication into Astro applications using middleware, SSR pages, island components, and API routes.
Contains scripts
Claude Code, Codex, Cursor +3 · about 850 tokens · pinned 6 days ago
clerk-billing
Clerk Billing enables subscription management in SaaS apps by rendering pricing tables and checkout drawers, configuring plans with feature entitlements, and…
Claude Code, Codex, Cursor +4 · about 4.9K tokens · pinned 6 days ago
Related in Frontend and design
clerk-react-patterns
This skill provides React SPA authentication patterns using Clerk's @clerk/react library for Vite and Create React App projects.
by ClerkOfficial publisher
Contains scripts
Claude Code, Codex, Cursor +3 · about 940 tokens · pinned 6 days ago
next-bundle-optimizer
Audits Next.js applications to identify and reduce browser bundle size and initial-load work by analyzing routes, dependencies and client outputs.
by VercelOfficial publisher
Claude Code, Codex, Cursor +4 · about 2.3K tokens · pinned 6 days ago
next-cache-components-optimizer
Optimize Next.js routes to display meaningful UI instantly on initial load or client navigation by encoding the optimization goal as a failing…
by VercelOfficial publisher
Claude Code, Codex, Cursor +4 · about 5.6K tokens · pinned 6 days ago
next-partial-prefetching-adoption
Enables Partial Prefetching in Next.js 16.3+ apps and guides adoption through auditing Link prefetch behavior, preserving UI in tests or manually, and…
by VercelOfficial publisher
Claude Code, Codex, Cursor +4 · about 6.7K tokens · pinned 6 days ago
remotion-interactivity
This skill teaches how to structure Remotion code so the Studio IDE recognizes and makes it interactive.
by RemotionOfficial publisher
Claude Code, Codex, Cursor +3 · about 2.4K tokens · pinned 6 days ago
remotion-interactivity
This skill teaches how to structure Remotion video components to unlock Studio interactivity features like click selection, drag-and-drop, resizing, rotation…
by RemotionOfficial publisher
Claude Code, Codex, Cursor +3 · about 4.8K tokens · pinned 6 days ago