BETTER-AUTH. UI
Plugins

API Key

Add programmatic API key management with create, copy, and revoke flows to your authentication settings.

The API key plugin adds programmatic API key management to your authentication UI. Users can create, copy, and revoke API keys from a security card in account settings. New keys can use a configurable expiration interval, and each listed key shows when it expires.

It contributes:

  • An <ApiKeys /> card to the security settings tab for user-owned keys
  • An <OrganizationApiKeys /> card to <OrganizationSettings /> for organization-owned keys (opt-in via apiKeyPlugin({ organization: true }). Requires the organization plugin and a matching server-side API key config)

Setup

Install the Better Auth plugin

Install the @better-auth/api-key package and add it to your Better Auth server config:

lib/auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  // ...
  plugins: [
    apiKey() 
  ]
})

Install the matching client plugin

Add apiKeyClient() to your auth client so authClient.apiKey.* methods are available:

lib/auth-client.ts
import { createAuthClient } from "better-auth/react"
import { apiKeyClient } from "@better-auth/api-key/client"

export const authClient = createAuthClient({
  plugins: [apiKeyClient()] 
})

Install the UI plugin

Run the shadcn CLI to install the API key components and the apiKeyPlugin() factory into your project:

npx shadcn@latest add @better-auth-ui/api-key

This drops the following into your codebase:

  • src/lib/auth/auth-plugin.ts: local AuthPlugin typing widener
  • src/lib/auth/api-key-plugin.ts: apiKeyPlugin() factory
  • src/components/auth/api-key/api-keys.tsx: the API keys security card
  • src/components/auth/api-key/api-key.tsx: individual API key row with delete control
  • src/components/auth/api-key/api-keys-empty.tsx: empty state shown when no keys exist
  • src/components/auth/api-key/api-key-skeleton.tsx: skeleton shown while keys are loading
  • src/components/auth/api-key/create-api-key-dialog.tsx: dialog for creating a new key
  • src/components/auth/api-key/new-api-key-dialog.tsx: dialog showing the newly created key with copy button
  • src/components/auth/api-key/delete-api-key-dialog.tsx: confirmation dialog for revoking a key
  • src/components/auth/api-key/organization-api-keys.tsx: owner-gated wrapper that renders <ApiKeys /> scoped to the active organization

Register the UI plugin

Pass apiKeyPlugin() to <AuthProvider>:

components/providers.tsx
import { AuthProvider } from "@/components/auth/auth-provider"
import { apiKeyPlugin } from "@/lib/auth/api-key-plugin"

import { authClient } from "@/lib/auth-client"

<AuthProvider
  authClient={authClient}
  plugins={[
    apiKeyPlugin() 
  ]}
>
  {children}
</AuthProvider>

Configure expiration

The create dialog offers 30 days, 90 days, and Never by default. It initially selects 30 days. Configure the choices through apiKeyPlugin():

components/providers.tsx
apiKeyPlugin({
  keyExpiration: {
    intervals: [7, 30, 90],
    defaultInterval: 30,
    allowNever: true
  }
})

intervals and defaultInterval use days. Better Auth receives the selected lifetime as seconds.

Keep the UI choices within the limits in your Better Auth server configuration:

lib/auth.ts
apiKey({
  keyExpiration: {
    minExpiresIn: 7,
    maxExpiresIn: 90,
    defaultExpiresIn: null
  }
})

When allowNever is enabled, selecting Never sends no custom interval. Better Auth will still apply defaultExpiresIn if the server defines one, so set allowNever: false in the UI when your server always requires expiration.

To remove the expiration field and rely entirely on the server default:

components/providers.tsx
apiKeyPlugin({ keyExpiration: false })

Components

<ApiKeys />

API keys

The security settings page shows <ApiKeys /> when the layout renders plugin securityCards. Add apiKeyPlugin() to plugins to provide this card.

Usage

import { ApiKeys } from "@/components/auth/api-key/api-keys"

<ApiKeys />

Props

Prop

Type

<OrganizationApiKeys />

A thin wrapper around <ApiKeys /> that resolves the active organization via useActiveOrganization and forwards its id. Rendered inside <OrganizationSettings /> only when the plugin is registered with { organization: true }.

To enable, opt in on the UI plugin and add a matching API key configuration to your Better Auth server config. The plugin uses a fixed configId of "organization", so the server entry must be { configId: "organization", references: "organization" }:

components/providers.tsx
import { AuthProvider } from "@/components/auth/auth-provider"
import { apiKeyPlugin } from "@/lib/auth/api-key-plugin"

import { authClient } from "@/lib/auth-client"

<AuthProvider
  authClient={authClient}
  plugins={[
    apiKeyPlugin({ organization: true }) 
  ]}
>
  {children}
</AuthProvider>
lib/auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"
import { organization } from "better-auth/plugins"

export const auth = betterAuth({
  // ...
  plugins: [
    organization(),
    apiKey([
      { configId: "default", references: "user" },
      { configId: "organization", references: "organization" } 
    ])
  ]
})

See the Better Auth docs for role-based permissions on organization-owned keys.

Usage

import { OrganizationApiKeys } from "@/components/auth/api-key/organization-api-keys"

<OrganizationApiKeys />

Props

Prop

Type

Options

Prop

Type

Localization

Prop

Type

Lifecycle controls

<ApiKeys /> lets users create, rename, and delete keys. The create form exposes the name, configuration, expiration, and organization. The list shows status, remaining requests, request usage, and the last request time as read-only values.

The built-in dialog does not show metadata because metadata belongs to the application. For metadata, build a custom form with useCreateApiKey. Map named fields or application state to the metadata object. Do not show a raw JSON editor. If metadata affects trusted behavior, validate it in a server route.

Better Auth reserves enablement, permissions, quotas, refill rules, and rate limits for server-side creation and updates. Configure those values in trusted server code instead of exposing them in the account UI.

apiKeyPlugin({
  configurations: [
    { id: "default", label: "Personal", organization: false },
    { id: "organization", label: "Organization", organization: true }
  ],
  pageSize: 20
})

The server must define every listed configId. Use useUpdateApiKey for a custom rename surface.

Last updated on

On this page