Clerk JavaScript SDK logo

clerk javascript sdk

Set up user authentication in vanilla JavaScript apps with Clerk

$ npx docs2skills add clerk-javascript-auth
SKILL.md

Clerk JavaScript SDK

Set up user authentication in vanilla JavaScript apps with Clerk

What this skill does

Clerk's JavaScript SDK provides a complete authentication system for vanilla JavaScript applications through prebuilt UI components and session management. It handles sign-up, sign-in, password reset, multi-factor authentication, and user profile management without requiring you to build custom forms or manage authentication state manually.

The SDK integrates with any JavaScript bundler (Vite, Webpack, Rollup) and provides methods to mount authentication components directly into your DOM. It automatically manages JWT tokens, session persistence, and user state across page refreshes. Clerk handles the security complexities of modern authentication including CSRF protection, secure cookie handling, and OAuth integrations.

Unlike framework-specific solutions, this vanilla SDK works with any JavaScript architecture - whether you're building a single-page application, adding auth to an existing site, or working without a frontend framework. The prebuilt components are fully customizable and handle edge cases like loading states, error handling, and accessibility.

Prerequisites

  • Node.js 14+ for development build tools
  • A bundler like Vite, Webpack, or Rollup (ES modules required)
  • Clerk account and publishable API key from dashboard.clerk.com
  • Modern browser with ES6+ support for end users

Quick start

npm create vite@latest my-auth-app
cd my-auth-app
npm install @clerk/clerk-js

Create .env file:

VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here

Update src/main.js:

import { Clerk } from '@clerk/clerk-js'

const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(publishableKey)
await clerk.load()

if (clerk.isSignedIn) {
  document.getElementById('app').innerHTML = `<div id="user-button"></div>`
  clerk.mountUserButton(document.getElementById('user-button'))
} else {
  document.getElementById('app').innerHTML = `<div id="sign-in"></div>`
  clerk.mountSignIn(document.getElementById('sign-in'))
}

Core concepts

Clerk Instance: The main class that manages authentication state and provides methods to interact with Clerk's services. You create one instance per application and call load() to initialize it asynchronously.

Prebuilt Components: Ready-made UI components like SignIn, SignUp, UserButton that handle all authentication flows. These mount to DOM elements and include styling, form validation, and error handling out of the box.

Session Management: Clerk automatically manages user sessions, storing encrypted tokens in secure cookies. The isSignedIn property and user object provide reactive access to authentication state.

Component Mounting: Instead of importing React components, you mount Clerk components to existing DOM elements using methods like mountSignIn() and mountUserButton(). This allows integration with any JavaScript architecture.

Key API surface

MethodDescription
new Clerk(publishableKey)Creates Clerk instance with your API key
clerk.load(options?)Initializes Clerk, loads user session
clerk.isSignedInBoolean indicating if user is authenticated
clerk.userCurrent user object with profile data
clerk.mountSignIn(element, props?)Renders sign-in form in DOM element
clerk.mountSignUp(element, props?)Renders sign-up form in DOM element
clerk.mountUserButton(element, props?)Renders user menu button
clerk.mountUserProfile(element, props?)Renders user profile management
clerk.signOut()Signs out current user
clerk.addListener(event, callback)Listens for auth state changes
clerk.navigate(to)Programmatic navigation after auth
clerk.redirectToSignIn()Redirect to hosted sign-in page

Common patterns

Protected routes with redirect:

if (!clerk.isSignedIn) {
  clerk.redirectToSignIn()
  return
}
// Render protected content

Listen for authentication changes:

clerk.addListener('user', (user) => {
  if (user) {
    // User signed in, update UI
    renderDashboard()
  } else {
    // User signed out, show login
    renderSignIn()
  }
})

Custom sign-up with redirect:

clerk.mountSignUp(signUpElement, {
  redirectUrl: '/dashboard',
  signInUrl: '/sign-in'
})

Conditional component mounting:

await clerk.load()
const targetDiv = document.getElementById('auth')

if (clerk.isSignedIn) {
  clerk.mountUserButton(targetDiv)
} else {
  clerk.mountSignIn(targetDiv)
}

Configuration

Load options (passed to clerk.load()):

  • routing: 'path' | 'hash' - URL routing strategy (default: 'path')
  • navigate: (to: string) => void - Custom navigation function
  • allowedRedirectOrigins: string[] - Allowed redirect URLs for security

Environment variables:

  • VITE_CLERK_PUBLISHABLE_KEY - Your Clerk publishable key (Vite)
  • CLERK_PUBLISHABLE_KEY - Generic environment variable name

Component props (passed to mount methods):

  • redirectUrl: string - Where to redirect after auth success
  • signUpUrl: string - Link between sign-in and sign-up pages
  • signInUrl: string - Link between sign-up and sign-in pages
  • appearance: object - Custom styling and theming options

Best practices

Always call clerk.load() before checking isSignedIn or accessing user to ensure session is initialized.

Use environment variables for publishable keys, never hardcode them in client-side code.

Implement loading states while clerk.load() completes - the promise can take time on slow networks.

Add error boundaries around component mounting since network issues can cause mount methods to throw.

Use clerk.addListener() to react to auth state changes rather than polling isSignedIn.

Keep one Clerk instance per application - don't create multiple instances as they won't share state.

For SPA routing, pass a custom navigate function to load() to integrate with your router.

Gotchas and common mistakes

  • Must await clerk.load(): Checking isSignedIn before load completes returns stale data
  • Publishable key format: Must start with pk_test_ or pk_live_, verify you're using publishable not secret key
  • Environment variable naming: Vite requires VITE_ prefix, other bundlers may differ
  • Mount element timing: Ensure DOM elements exist before calling mount methods or they'll throw
  • Multiple mounts: Mounting to the same element twice without unmounting causes duplicate components
  • Session persistence: Signing out in one tab doesn't immediately update other tabs - use listeners
  • HTTPS requirement: Clerk requires HTTPS in production, localhost works for development
  • Bundle size: Import only from @clerk/clerk-js, not framework-specific packages
  • Component lifecycle: Mounted components handle their own cleanup, but manual event listeners need cleanup
  • Redirect loops: Setting redirectUrl to current page can cause infinite redirects
  • Cross-origin issues: Ensure your domain is added to Clerk dashboard's allowed origins
  • Server-side rendering: This SDK is client-only, use @clerk/nextjs or similar for SSR frameworks