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.

referencescreate-clerk-client.md

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

createClerkClient() -- Vanilla JS and Service Workers

When to Use

Use createClerkClient() when:

  • Your extension doesn't use React
  • You need Clerk in a background service worker
  • You need Clerk in a content script context (via message passing from background)
  • You want to keep sessions fresh without a visible popup

Import from @clerk/chrome-extension/client, NOT from @clerk/chrome-extension.

Background Service Worker

The key option is background: true. This tells Clerk to refresh the session token continuously, even when no popup or side panel is open. Without it, tokens expire after 60 seconds of the UI being closed.

src/background/index.ts:

import { createClerkClient } from '@clerk/chrome-extension/client'

const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY

if (!publishableKey) {
  throw new Error('Missing 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 service worker] Error:', JSON.stringify(error))
      sendResponse({ token: null })
    })
  return true
})

The listener MUST return true to keep the message channel open for the async sendResponse call.

Requesting the Token from a Tab or Content Script

// tabs/my-tab.tsx or a content script
async function getTokenFromBackground(): Promise<string | null> {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage({ type: 'GET_TOKEN' }, (response) => {
      resolve(response?.token ?? null)
    })
  })
}

async function makeAuthenticatedRequest() {
  const token = await getTokenFromBackground()
  if (!token) {
    console.warn('User not signed in')
    return
  }

  const res = await fetch('https://api.example.com/me', {
    headers: { Authorization: `Bearer ${token}` },
  })

  return res.json()
}

Vanilla JS Popup (no React)

For popups or side panels that use plain TypeScript instead of React:

src/popup.ts:

import { createClerkClient } from '@clerk/chrome-extension/client'

const publishableKey = process.env.CLERK_PUBLISHABLE_KEY
const EXTENSION_URL = chrome.runtime.getURL('.')
const POPUP_URL = `${EXTENSION_URL}popup.html`

const clerk = createClerkClient({ publishableKey })
const contentEl = document.getElementById('content') as HTMLDivElement

function render() {
  const email = clerk.user?.primaryEmailAddress?.emailAddress
  contentEl.textContent = email ?? 'Not signed in'
}

clerk.load({
  afterSignOutUrl: POPUP_URL,
  signInForceRedirectUrl: POPUP_URL,
  signUpForceRedirectUrl: POPUP_URL,
  allowedRedirectProtocols: ['chrome-extension:'],
}).then(() => {
  clerk.addListener(render)
  render()
})

allowedRedirectProtocols: ['chrome-extension:'] is required to allow redirects to chrome-extension:// URLs.

createClerkClient() Options

Option Type Description
publishableKey string Required. Your Clerk publishable key.
background boolean Set true in service workers to keep sessions fresh.
syncHost string The web app domain to sync auth from (headless extensions).

Making Authenticated API Calls from Background

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

  if (!clerk.session) return

  const token = await clerk.session.getToken()

  const res = await fetch('https://api.yourapp.com/data', {
    headers: { Authorization: `Bearer ${token}` },
  })

  return res.json()
}

Docs

createClerkClient() reference

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.