Plugin composition: server and client plugins
Plugins in Better Auth can be server-side, client-side, or both. Server plugins are added via the `plugins` array in the `betterAuth()` configuration. Client plugins are added via the `plugins` array when creating the auth client with `createAuthClient()`. Most plugins require both server and client versions to function correctly.
Server plugin structure: BetterAuthPlugin interface
A server plugin is an object satisfying the `BetterAuthPlugin` interface. The only required property is `id`, which is a unique identifier for the plugin. Plugins are typically implemented as functions to allow passing options, following the pattern of built-in plugins.
Server plugin capabilities
A server plugin can: create custom endpoints using `createAuthEndpoint()`; extend database tables with custom schemas; use middleware to target groups of routes; use hooks to target specific routes or requests; use `onRequest` or `onResponse` to affect all requests/responses; create custom rate-limit rules.
Creating endpoints in plugins
Endpoints are created using `createAuthEndpoint()` imported from `better-auth/api`. The function takes three parameters: the endpoint path (string in kebab-case), an options object specifying HTTP method and request schema, and an async handler function receiving a context object. The context object provides access to Better Auth specific properties like `options`, `db`, `adapter`, `baseURL`, `session`, `secret`, `authCookie`, `logger`, `internalAdapter`, `trustedOrigins`, and `isTrustedOrigin` helper function.
Endpoint path and method rules
Endpoint paths must use kebab-case and should include the plugin name as a prefix to avoid conflicts (e.g., `/my-plugin/hello-world`). Only POST and GET methods are permitted. POST methods should be used for functions that modify data; GET methods for functions that fetch data.
Plugin schema definition
A plugin defines database tables via the `schema` object where keys are table names and values are schema definitions. Each table definition includes a `fields` object where keys are column names. Field definitions can specify: `type` (string, number, boolean, or date), `required` (default: true), `unique` (default: false), and `references` with `model`, `field`, and `onDelete` (default: cascade) properties for foreign keys. Additional schema properties include `disableMigration` (default: false) to prevent table migration.
Schema field extension for user and session tables
When additional fields are added to the `user` or `session` tables via plugin schemas, the types are automatically inferred on `getSession` and `signUpEmail` calls, and all user/session-returning endpoints include those fields with proper TypeScript typing.
Schema security warning for sensitive data
Do not store sensitive information in the `user` or `session` tables in plugin schemas. Create a new table if sensitive information needs to be stored.
Plugin hooks structure
Hooks are added via a `hooks` object containing `before` and `after` arrays. Each hook object has a `matcher` function that receives a context object and returns a boolean, and a `handler` function created with `createAuthMiddleware()`. The matcher determines when the hook runs; the handler executes code before (or modifies the response in after hooks).
Plugin middleware vs hooks execution
Middleware only runs on API requests from a client; if an endpoint is invoked directly, the middleware will not run. Hooks run regardless of whether the request comes from a client or direct server invocation. Both can stop the request by throwing an `APIError` or returning a `Response` object.
Middleware configuration in plugins
Middleware is added via a `middlewares` array with objects containing a `path` property (string or path matcher using better-call's path-matching system) and a `middleware` property (function created with `createAuthMiddleware()`).
onRequest hook behavior
The `onRequest` function is called right before a request is made, receiving `request` and `context` parameters. It can: continue the request normally by returning nothing, interrupt the request by returning an object with a `response` property containing a Response object, or modify the request by returning a modified `request` object.
onResponse hook behavior
The `onResponse` function executes immediately after a response is returned, receiving `response` and `context` parameters. It can modify the response by returning a modified response object, or send the response as-is by returning nothing.
Trusted origins in plugins
Plugins can define a `trustedOrigins` array of allowed origins. The `isTrustedOrigin()` method available on the auth context validates URLs against this configuration and the main Better Auth trusted origins setting. It accepts an `allowRelativePaths` option to control whether relative paths are permitted.
getSessionFromCtx helper function
The `getSessionFromCtx()` function, imported from `better-auth/api`, allows retrieval of the client's session data by passing the auth middleware's context object. Used within hook handlers to access session information.
sessionMiddleware helper
The `sessionMiddleware` helper, imported from `better-auth/api`, checks if the client has a valid session. If valid, it adds the session data to the context object as `ctx.context.session`. Used by passing `sessionMiddleware` to the `use` array in endpoint options.
requireResourceOwnership middleware
The `requireResourceOwnership` middleware, imported from `better-auth/api`, loads a resource by ID and verifies it belongs to the authenticated user. Used after `sessionMiddleware` for user-owned resource endpoints. Configuration object specifies: `model` (resource type), `idParam` (parameter name), `idSource` ('body', 'query', or 'path'), with optional `ownerField`, `notFoundError`, and `forbiddenError` properties.
requireOrgRole middleware
The `requireOrgRole` middleware, imported from `better-auth/api`, verifies the authenticated user is a member of a specific organization and optionally has one of allowed roles. Used after `sessionMiddleware` for organization-scoped endpoints. Configuration specifies: `orgIdParam` (parameter name), `orgIdSource` ('body', 'query', or 'path'), with optional `allowedRoles` array. If `allowedRoles` is omitted, any organization member is accepted. Users with multiple roles are authorized if any role matches.
Client plugin structure
A client plugin is an object satisfying the `BetterAuthClientPlugin` interface with a required `id` property matching the server plugin's ID. If server plugin endpoints need client-side calls, add `$InferServerPlugin: {} as ReturnType<typeof serverPlugin>` to infer endpoint interfaces.
Client endpoint inference from server plugin
Client plugins can infer endpoints from server plugins using the `$InferServerPlugin` key. Endpoint paths are converted from kebab-case to camelCase (e.g., `/my-plugin/hello-world` becomes `myPlugin.helloWorld()`).
Client plugin getActions function
The `getActions` function in a client plugin receives the `fetch` function from the client (powered by Better Fetch) and should return an object with custom action methods. Each action should accept one argument plus an optional second argument for `BetterFetchOption` for additional fetch options. Actions should return an object with `data` and `error` keys.
Client plugin getAtoms function
The `getAtoms` function in a client plugin is called with the `fetch` function and should return an object containing nanostores atoms. Atoms are created using the `atom()` function from nanostores and provide hooks like `useSession` via each framework's `useStore` hook.
Client plugin pathMethods override
The `pathMethods` object in a client plugin allows overriding the inferred HTTP method for endpoints. Keys are endpoint paths, values are 'POST' or 'GET'. By default, GET is used for endpoints without a required body, POST for those with a body.
Client plugin fetchPlugins array
A client plugin can include a `fetchPlugins` array to pass Better Fetch plugins to customize fetch behavior. Plugins are configured using better-fetch's plugin system.
customSession plugin for session response customization
The customSession plugin allows customization of session response when calling getSession or useSession. Pass an async callback function that receives user and session objects and returns modified user, session, and additional fields. Example: customSession(async ({ user, session }) => ({ roles, user: {...user, newField: 'value'}, session }))
Custom session function called on every fetch
Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called, even if the session is cached.
shouldMutateListDeviceSessionsEndpoint option
The customSession plugin accepts a third parameter with shouldMutateListDeviceSessionsEndpoint option. Set to true to mutate the response of the /multi-session/list-device-sessions endpoint from the multi-session plugin. By default, this is false (does not mutate the response).