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.

referencescontent-scripts.md

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

Content Scripts

Constraint

Content scripts run in an isolated JavaScript world injected into web pages. They cannot:

  • Use Clerk React hooks
  • Call Clerk APIs directly (Clerk enforces strict origin restrictions -- content scripts could run on any domain)
  • Access the extension's React context

Use message passing to request auth state from the background service worker.

Pattern: Request Token from Background

src/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 isSignedIn(): Promise<boolean> {
  const token = await getToken()
  return token !== null
}

async function injectUI() {
  const signedIn = await isSignedIn()

  if (!signedIn) {
    console.log('User not signed in, skipping injection')
    return
  }

  const overlay = document.createElement('div')
  overlay.id = 'my-extension-overlay'
  document.body.appendChild(overlay)
}

injectUI()

src/background/index.ts:

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) => {
  if (request.type === 'GET_TOKEN') {
    getToken()
      .then((token) => sendResponse({ token }))
      .catch(() => sendResponse({ token: null }))
    return true
  }
})

Pattern: Authenticated Fetch from Content Script

// content.ts
async function fetchUserData() {
  const token = await getToken()
  if (!token) return null

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

  return res.json()
}

Manifest Permissions

package.json (Plasmo):

{
  "manifest": {
    "permissions": ["storage", "tabs"],
    "host_permissions": ["<all_urls>"]
  }
}

For content scripts on specific domains only:

{
  "manifest": {
    "permissions": ["storage"],
    "host_permissions": ["https://specific-site.com/*"]
  }
}

Content Script Registration (Plasmo)

A file named content.ts or content.tsx at the project root is auto-registered as a content script matching all URLs.

For multiple content scripts with different match patterns, use package.json:

{
  "manifest": {
    "content_scripts": [
      {
        "matches": ["https://specific-site.com/*"],
        "js": ["content.js"]
      }
    ]
  }
}

Why Clerk Can't Be Used Directly in Content Scripts

Clerk enforces strict allowed origins for API requests. A content script can be injected into any domain (e.g., https://github.com, https://google.com). There is no way to add all possible domains to Clerk's allowed origins, so direct Clerk usage in content scripts is blocked by design.

Docs

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.