Chrome Extension Quickstart (JavaScript)
Before you start
Example repository
Enable Native API
In the Clerk Dashboard, navigate to the Native applications page and ensure that the Native API is enabled. This is required to integrate Clerk in your native application.
Configure your authentication options
When creating your Clerk application in the Clerk Dashboard, your authentication options will depend on how you configure your Chrome Extension. Chrome Extensions can be used as a popup, a side panel, or in conjunction with a web app. Popups and side panels have limited authentication options. Learn more about what options are available
This guide will use a popup.
Create a new project
Create a new project directory and initialize a package.json file.
mkdir clerk-chrome-extension-js
cd clerk-chrome-extension-js
npm init -ymkdir clerk-chrome-extension-js
cd clerk-chrome-extension-js
pnpm init -ymkdir clerk-chrome-extension-js
cd clerk-chrome-extension-js
yarn init -ymkdir clerk-chrome-extension-js
cd clerk-chrome-extension-js
bun init -yThen set the type field to "module" in package.json so you can use ES module syntax.
{
"type": "module"
}Add your Clerk to your .env file.
- In the Clerk Dashboard, navigate to the API keys page.
- In the Quick Copy section, copy your Clerk .
- Paste your key into your
.envfile.
The final result should resemble the following:
CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEYInstall dependencies
Install the Clerk Chrome Extension SDK and the development dependencies needed to build the extension.
npm install @clerk/chrome-extension
npm install -D esbuild typescript @types/chromepnpm add @clerk/chrome-extension
pnpm add -D esbuild typescript @types/chromeyarn add @clerk/chrome-extension
yarn add --dev esbuild typescript @types/chromebun add @clerk/chrome-extension
bun add --dev esbuild typescript @types/chromeesbuild— A fast JavaScript bundler that compiles TypeScript and bundles your code.typescript— Adds type checking for your source files.@types/chrome— Type definitions for the Chrome Extension APIs.
Configure TypeScript and esbuild
Create a tsconfig.json file for TypeScript configuration.
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"outDir": "build",
"rootDir": "src",
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}Create an esbuild.config.mjs file that bundles your TypeScript source code and injects the CLERK_PUBLISHABLE_KEY environment variable at build time.
import esbuild from 'esbuild'
process.loadEnvFile()
if (!process.env.CLERK_PUBLISHABLE_KEY) {
throw new Error('Missing CLERK_PUBLISHABLE_KEY in .env')
}
const watch = process.argv.includes('--watch')
/** @type {import('esbuild').BuildOptions} */
const options = {
entryPoints: ['./src/popup.ts'],
outfile: './build/popup.js',
bundle: true,
format: 'iife',
platform: 'browser',
target: 'es2022',
sourcemap: true,
define: {
'process.env.CLERK_PUBLISHABLE_KEY': JSON.stringify(process.env.CLERK_PUBLISHABLE_KEY),
},
}
if (watch) {
const ctx = await esbuild.context(options)
await ctx.watch()
console.log('Watching for changes...')
} else {
await esbuild.build(options)
}Add build and dev scripts to your package.json.
{
"scripts": {
"build": "node esbuild.config.mjs",
"dev": "node esbuild.config.mjs --watch"
}
}Create the extension files
Create a build/ directory for the extension's static files and bundled output.
mkdir buildCreate a manifest.json file for the Chrome Extension manifest. This declares the extension's name, version, permissions, and popup entry point.
{
"manifest_version": 3,
"name": "Clerk Extension JS Demo",
"description": "A Chrome extension demo using @clerk/chrome-extension with plain TypeScript.",
"version": "0.0.1",
"permissions": ["cookies", "storage"],
"host_permissions": ["http://localhost/*"],
"action": {
"default_title": "Clerk Extension Demo",
"default_popup": "popup.html"
}
}Create the popup.html file that serves as the extension popup's entry point.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk Extension Demo</title>
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<div id="app">
<h1>Clerk JS Chrome Extension Quickstart</h1>
<div id="content"></div>
<nav id="nav"></nav>
</div>
<script src="popup.js"></script>
</body>
</html>Create the popup.css file for styling the popup.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 600px;
height: 600px;
background: #111;
color: #fff;
font-family: system-ui, sans-serif;
}
#app {
display: flex;
flex-direction: column;
min-height: 600px;
}
h1 {
padding: 2rem 1rem;
}
#content {
padding: 1rem;
flex: 1;
}
#nav {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: #222;
}
#nav:has(#sign-out-btn) {
justify-content: space-between;
}
button {
background: #6c47ff;
color: #fff;
border: none;
border-radius: 6px;
padding: 0.5rem 1.25rem;
font-size: 0.875rem;
cursor: pointer;
}Update .gitignore
Create a .gitignore file that ignores node_modules/, build artifacts, and environment files. The static files in build/ (manifest.json, popup.html, popup.css) remain tracked.
node_modules/
build/*.js
build/*.js.map
.envCreate the popup script
Create a src/ directory.
mkdir srcCreate an env.d.ts file that declares the process.env type so TypeScript can resolve the environment variable injected by esbuild at build time.
declare const process: { env: { CLERK_PUBLISHABLE_KEY: string } }The popup.ts file is the main script for the extension popup that handles authentication with Clerk.
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
const navEl = document.getElementById('nav') as HTMLDivElement
function render() {
contentEl.innerHTML = ''
navEl.innerHTML = ''
if (clerk.user) {
const info = document.createElement('div')
const labelSpan = document.createElement('p')
labelSpan.textContent = 'Session ID'
const valueSpan = document.createElement('p')
valueSpan.textContent = clerk.session?.id ?? ''
info.appendChild(labelSpan)
info.appendChild(valueSpan)
contentEl.appendChild(info)
const userBtnEl = document.createElement('div')
navEl.appendChild(userBtnEl)
clerk.mountUserButton(userBtnEl)
const signOutBtn = document.createElement('button')
signOutBtn.textContent = 'Sign Out'
signOutBtn.id = 'sign-out-btn'
signOutBtn.addEventListener('click', () => {
clerk.signOut({ redirectUrl: POPUP_URL })
})
navEl.appendChild(signOutBtn)
} else {
const signInBtn = document.createElement('button')
signInBtn.textContent = 'Sign In'
signInBtn.id = 'sign-in-btn'
signInBtn.addEventListener('click', () => {
clerk.openSignIn({})
})
navEl.appendChild(signInBtn)
}
}
clerk
.load({
afterSignOutUrl: POPUP_URL,
signInForceRedirectUrl: POPUP_URL,
signUpForceRedirectUrl: POPUP_URL,
allowedRedirectProtocols: ['chrome-extension:'],
})
.then(() => {
clerk.addListener(render)
render()
})Key concepts in this file:
createClerkClient()— Creates a Clerk client instance for use outside of React. This is the main entry point for the Chrome Extension SDK's vanilla JavaScript API.allowedRedirectProtocols— Set to['chrome-extension:']to allow Clerk to redirect tochrome-extension://URLs. Without this, redirects after sign-in or sign-up will fail.chrome.runtime.getURL('.')— Gets the base URL of the extension (chrome-extension://<CRX_ID>/). This is used to construct redirect URLs so Clerk navigates back to the popup after authentication.clerk.addListener(render)— Registers a callback that fires whenever the Clerk client state changes (for example, after sign-in or sign-out), keeping the UI in sync.
Build and load your extension
Run the dev command to bundle your TypeScript source into build/popup.js.
npm run devpnpm run devyarn devbun run devTo load your Chrome Extension, follow these steps:
- Open Chrome or a Chromium-based browser and navigate to
chrome://extensions. - In the top-right, enable Developer mode.
- In the top-left, select Load unpacked.
- Navigate to where your project is located and select the
build/folder, then press Select. - Your extension will now be loaded and shown in the list of extensions.
Open the extension popup and confirm that the Sign In button appears. Select it to open the sign-in flow, then sign in and confirm that your user information and the Sign Out button appear.
Sync your Chrome Extension with your web app
Learn how to configure your Chrome Extension to sync user authentication with your web app.
createClerkClient()
Learn how to use Clerk's createClerkClient() function in a background service worker to ensure that the user's session is always fresh.
Deploy a Chrome Extension to production
Learn how to deploy your Clerk Chrome Extension to production.
Configure a consistent CRX ID
Learn how to configure a consistent CRX ID to ensure your extension has a stable, unchanging key.
Clerk Chrome Extension SDK Reference
Learn about the Clerk Chrome Extension SDK and how to integrate it into your app.
Feedback
Last updated on