All skills
clerk avatar

/clerk-chrome-extension-patterns

@792a318 official
by clerkclerk/skills83 stars
5

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.

Use this Skill: https://skilld.dev/gh/clerk/skills/clerk-chrome-extension-patterns

This session only. Nothing lands on disk.

referencesheadless-extension.md

≈953 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Headless Extension (no popup, no side panel)

Use Case

An extension that runs entirely in the background -- no UI, no popup, no side panel. It syncs auth state from a companion web app and acts on behalf of the signed-in user automatically.

Examples:

  • Auto-fill tools that activate when the user visits certain pages
  • Extensions that sync data in the background when the user is signed in on the web app
  • Developer tools that call your API without user interaction

Requirements

  • The user signs in via your web app (not the extension)
  • The extension reads auth state from the web app's session cookie
  • syncHost + createClerkClient({ background: true }) combination

Background Service Worker

src/background/index.ts:

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

if (!publishableKey || !syncHost) {
  throw new Error('Missing publishable key or sync host')
}

async function getAuthenticatedUser() {
  const clerk = await createClerkClient({
    publishableKey,
    syncHost,
    background: true,
  })

  return clerk.user
}

async function getSessionToken(): Promise<string | null> {
  const clerk = await createClerkClient({
    publishableKey,
    syncHost,
    background: true,
  })

  if (!clerk.session) return null

  return await clerk.session.getToken()
}

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.status !== 'complete') return

  const token = await getSessionToken()
  if (!token) return

  await fetch('https://api.yourapp.com/page-visit', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url: tab.url }),
  })
})

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

Manifest Configuration

package.json:

{
  "manifest": {
    "key": "$CRX_PUBLIC_KEY",
    "permissions": ["cookies", "storage", "tabs"],
    "host_permissions": [
      "$PLASMO_PUBLIC_CLERK_SYNC_HOST/*",
      "$CLERK_FRONTEND_API/*"
    ]
  }
}

host_permissions for the sync host domain is what allows the extension to read the Clerk session cookie from the web app.

Register Extension in Clerk

The extension ID must be in your web app instance's allowed origins:

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"]}'

Key Difference from Popup + syncHost

In a popup extension with syncHost, the user can also sign in directly via the popup (email/password, OTP). In a headless extension, there is no UI at all -- the user MUST sign in via the web app. The extension only reads auth state.

Debugging

To verify auth state is syncing:

const clerk = await createClerkClient({ publishableKey, syncHost, background: true })
console.log('User:', clerk.user?.emailAddresses[0]?.emailAddress ?? 'Not signed in')
console.log('Session:', clerk.session?.id ?? 'No session')

If user is null despite being signed in on the web app, check:

  1. host_permissions includes the sync host domain
  2. The extension ID is in Clerk's allowed origins
  3. The syncHost value matches the Clerk Frontend API URL (not the web app's main domain)

Source: SKILL.md on GitHub

1 alert15d4 checks · Risk CRITICAL
  • Gen Agent Trust Hub16d

    The skill is safe. It provides reference documentation and template files for implementing Clerk authentication within Chrome Extensions using the Plasmo framework. The URL flagged by the automated scanner is a standard, benign placeholder domain commonly used in developer documentation to represent a custom user domain.

  • Socket15d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 792a318. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 6 months ago
What it can do
Network
All 1 allowed tools
WebFetch
Other metadata
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"
  ]
}
  • clerk
  • chrome-extension
  • authentication
  • plasmo
  • oauth
  • saml
  • passkeys
  • service-worker
  • popup
  • sidepanel

README badge

README badge for clerk/skills/clerk-chrome-extension-patterns

Guides Chrome extension authentication with Clerk's `@clerk/chrome-extension` library, covering popup/side panel setup, OAuth delegation via syncHost to a web app, service worker token management with createClerkClient, and stable extension ID configuration. Targets Plasmo-based extensions and addresses the specific constraints of extension environments — email links don't work in popups, OAuth requires web app delegation, and bot protection must be disabled.

Generated from the current SKILL.md.

Does this skill support OAuth and SAML in Chrome extension popups?
No. OAuth (Google, GitHub, etc.) and SAML are not supported in popups or side panels. Use syncHost to delegate authentication to your web app instead.
Can I use Clerk React hooks in service workers and content scripts?
No. Service workers and content scripts have no access to Clerk React hooks. Use createClerkClient() for service workers or message passing from content scripts to the background service worker.
What happens to my extension ID when I rebuild?
Without a stable CRX ID configured via a pinned key in manifest, Chrome generates a new ID on each rebuild, breaking your allowed origins. Configure a stable key using Plasmo Itero before deploying.
How do I sync authentication between my Chrome extension and web app?
Use syncHost to read the Clerk session cookie from your web app's domain. Configure host_permissions for both the sync host and Clerk API, then add your extension ID to Clerk's allowed origins via the API.
Do email magic links work in Chrome extension popups?
No. Magic link sign-in does not work in popups because the popup closes when the user clicks outside, resetting sign-in state. Use email OTP, password, passkeys, or delegate to your web app via syncHost instead.

Generated from the current SKILL.md. These answers refresh after source changes.