# Chrome Extension Quickstart (JavaScript)

**Example Repository**

- [Chrome Extension JS Quickstart Repo](https://github.com/clerk/clerk-chrome-extension-js-quickstart)

**Before you start**

- [Set up a Clerk application](https://clerk.com/docs/getting-started/quickstart/setup-clerk.md)

1. ## Enable Native API

   In the Clerk Dashboard, navigate to the [**Native applications**](https://dashboard.clerk.com/~/native-applications) page and ensure that the Native API is enabled. This is required to integrate Clerk in your native application.
2. ## 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 the [Chrome Extension authentication options](https://clerk.com/docs/reference/chrome-extension/overview.md#authentication-options).

   This guide will use a popup.
3. ## Create a new project

   Create a new project directory and initialize a `package.json` file.

   ```npm
   mkdir clerk-chrome-extension-js
   cd clerk-chrome-extension-js
   npm init -y
   ```

   Then set the `type` field to `"module"` in `package.json` so you can use ES module syntax.

   filename: package.json

   ```json
   {
     "type": "module"
   }
   ```
4. ## Set your Clerk API key

   Add your Clerk Publishable Key to your `.env` file.

   1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/~/api-keys) page.
   2. In the **Quick Copy** section, copy your Clerk Publishable Key.
   3. Paste your key into your `.env` file.

   The final result should resemble the following:

   filename: .env

   ```env
   CLERK_PUBLISHABLE_KEY={{pub_key}}
   ```
5. ## Install dependencies

   Install the Clerk Chrome Extension SDK and the development dependencies needed to build the extension.

   ```npm
   npm install @clerk/chrome-extension
   npm install -D esbuild typescript @types/chrome
   ```

   - `esbuild` — 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.
6. ## Configure TypeScript and esbuild

   Create a `tsconfig.json` file for TypeScript configuration.

   filename: tsconfig.json

   ```json
   {
     "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.

   filename: esbuild.config.mjs

   ```js
   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`.

   filename: package.json

   ```json
   {
     "scripts": {
       "build": "node esbuild.config.mjs",
       "dev": "node esbuild.config.mjs --watch"
     }
   }
   ```
7. ## Create the extension files

   Create a `build/` directory for the extension's static files and bundled output.

   filename: terminal

   ```bash
   mkdir build
   ```

   Create a `manifest.json` file for the Chrome Extension manifest. This declares the extension's name, version, permissions, and popup entry point.

   filename: build/manifest.json

   ```json
   {
     "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.

   filename: build/popup.html

   ```html
   <!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.

   filename: build/popup.css

   ```css
   * {
     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;
   }
   ```
8. ## 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.

   filename: .gitignore

   ```text
   node_modules/
   build/*.js
   build/*.js.map
   .env
   ```
9. ## Create the popup script

   Create a `src/` directory.

   filename: terminal

   ```bash
   mkdir src
   ```

   Create an `env.d.ts` file that declares the `process.env` type so TypeScript can resolve the environment variable injected by esbuild at build time.

   filename: src/env.d.ts

   ```ts
   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.

   filename: src/popup.ts

   ```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
   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 to `chrome-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.
10. ## Build and load your extension

    Run the dev command to bundle your TypeScript source into `build/popup.js`.

    ```npm
    npm run dev
    ```

    To load your Chrome Extension, follow these steps:

    1. Open Chrome or a Chromium-based browser and navigate to `chrome://extensions`.
    2. In the top-right, enable **Developer mode**.
    3. In the top-left, select **Load unpacked**.
    4. Navigate to where your project is located and select the `build/` folder, then press **Select**.
    5. 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.

    > After signing in, your popup may close. This is expected behavior for Chrome Extension popups. Reopening the popup will show your signed-in state.

## Next steps

Learn how to sync auth with your web app, keep sessions fresh with `createClerkClient()`, and prepare your extension for production using the following guides.

- [Sync your Chrome Extension with your web app](https://clerk.com/docs/guides/sessions/sync-host.md): Learn how to configure your Chrome Extension to sync user authentication with your web app.
- [createClerkClient()](https://clerk.com/docs/reference/chrome-extension/create-clerk-client.md): Learn how to use Clerk's&#x20;
- [Deploy a Chrome Extension to production](https://clerk.com/docs/guides/development/deployment/chrome-extension.md): Learn how to deploy your Clerk Chrome Extension to production.
- [Configure a consistent CRX ID](https://clerk.com/docs/guides/development/configure-consistent-crx-id.md): Learn how to configure a consistent CRX ID to ensure your extension has a stable, unchanging key.

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
