Creating a New Plugin
Relevant source files
The following files were used as context for generating this wiki page:Plugins are the fundamental building blocks for extending the functionality of the Better Auth Kit. They provide a structured way to introduce new features, such as custom API endpoints, database schema modifications, and hooks into the authentication lifecycle. This modular architecture allows developers to compose complex authentication and user management systems from smaller, reusable pieces.
A plugin is essentially a JavaScript object that conforms to the BetterAuthPlugin interface. It can be as simple as adding a single database field or as complex as a full invitation system with multiple API endpoints and custom logic. This document outlines the core concepts and structure required to build a new plugin.
Plugin Structure
At its core, a plugin is an object with a set of predefined properties that the Better Auth system recognizes and integrates. The main factory function for a plugin typically accepts an options object for configuration and returns an object satisfying the BetterAuthPlugin type.
Sources: packages/plugins/app-invite/src/index.ts:153-838, packages/plugins/legal-consent/src/index.ts:155-230
Core Properties
The following diagram illustrates the main properties of a plugin object.
This diagram shows the key properties of a BetterAuthPlugin object.
| Property | Type | Description |
|---|---|---|
id | string | A unique identifier for the plugin (e.g., "app-invite", "legal-consent"). |
schema | object | Defines new database tables or extends existing ones. |
endpoints | object | A collection of API endpoints exposed by the plugin. |
hooks | object | Functions that execute at specific points in the auth lifecycle (e.g., before sign-up). |
rateLimit | array | Defines rate-limiting rules for the plugin's endpoints. |
$ERROR_CODES | object | A map of constant error codes specific to the plugin. |
Sources: packages/plugins/app-invite/src/index.ts:157, packages/plugins/app-invite/src/index.ts:803-836, packages/plugins/legal-consent/src/index.ts:157, packages/plugins/legal-consent/src/index.ts:194-228, packages/plugins/waitlist/src/index.ts:40-42
Packaging
Each plugin is typically its own package within the packages/plugins/ directory. The package.json file defines its dependencies, build scripts, and entry points.
package.json Key | Description |
|---|---|
name | The npm package name, e.g., @better-auth-kit/app-invite. |
main | The main entry point for the compiled JavaScript (./dist/index.js). |
types | The TypeScript declaration file (./dist/index.d.ts). |
peerDependencies | Specifies better-auth as a required peer. |
scripts | Contains build and dev scripts, usually running bun build.ts. |
exports | Defines module exports, often including a ./client export for client-side code. |
Sources: packages/plugins/app-invite/package.json:1-46
Defining the Schema
Plugins can introduce new database tables or add fields to existing tables like user. This is done through the schema property. The schema definition is adapter-agnostic but provides the necessary metadata for adapters (like the Convex adapter) to generate the final database schema.
Sources: packages/plugins/app-invite/src/index.ts:803-836, packages/adapters/convex/src/generate-schema/generate-schema-builder.ts:80-117
Schema Structure
The schema object contains keys for each table the plugin manages. Each key maps to an object with a modelName and a fields object.
// Example from app-invite/src/index.ts
schema: {
appInvitation: {
modelName: options?.schema?.appInvitation?.modelName,
fields: {
// ... field definitions
inviterId: {
type: "string",
references: {
model: "user",
field: "id",
},
required: true,
},
},
},
},
Sources: packages/plugins/app-invite/src/index.ts:803-836
The diagram below shows the relationship defined in the app-invite plugin between the new appInvitation table and the core user table.
This ER diagram illustrates that a user can have multiple appInvitation records.
Field Attributes
Each field in the schema is defined by an object with several attributes.
| Attribute | Type | Description |
|---|---|---|
type | string | The data type (e.g., "string", "boolean", "date", "number", "string[]"). |
required | boolean | Whether the field is mandatory. |
fieldName | string | An optional override for the database column name. |
references | object | Defines a foreign key relationship to another model. |
defaultValue | any | A default value for the field. |
sortable | boolean | Indicates if the field can be used for sorting in list queries. |
Sources: packages/plugins/app-invite/src/index.ts:811-835, packages/plugins/legal-consent/src/index.ts:120-153
Creating API Endpoints
Plugins add functionality by exposing API endpoints. The endpoints property of a plugin is a map where keys are method names and values are endpoint definitions created with the createAuthEndpoint helper.
Sources: packages/plugins/app-invite/src/index.ts:158-161, packages/cli/src/commands/db/database-explorer-plugin.ts:29-40
Endpoint Definition
An endpoint is defined by its route, HTTP method, input validation schema (zod), optional middleware, and a request handler function.
The following flowchart shows the lifecycle of a request to a plugin endpoint.
This flowchart shows the processing steps for an incoming API request.
Sources: packages/plugins/app-invite/src/index.ts:162-167, packages/plugins/feedback/src/index.ts:77-88
Example: createAppInvitation
The app-invite plugin defines an endpoint to create invitations. It uses sessionMiddleware to ensure the user is authenticated, validates the request body with zod, and then executes the logic to create an invitation record.
// Simplified from app-invite/src/index.ts
createAppInvitation: createAuthEndpoint(
"/invite-user",
{
method: "POST",
use: [sessionMiddleware],
body: z.object({
email: z.string().optional(),
// ... other fields
}),
},
async (ctx) => {
// ... handler logic
const session = ctx.context.session;
// ... check permissions
const adapter = getAppInviteAdapter(ctx.context, options);
const invitation = await adapter.createInvitation(/* ... */);
return ctx.json(invitation);
},
),
Sources: packages/plugins/app-invite/src/index.ts:159-246
Using Hooks
Hooks allow plugins to inject logic into core authentication flows, such as user sign-up. The hooks property is an array of hook objects, each with a matcher to specify which path it applies to and a handler to execute.
The legal-consent plugin uses a before hook to verify that legal terms have been accepted before a user is created.
Sources: packages/plugins/legal-consent/src/index.ts:195-225
Hook Execution Flow
This sequence diagram shows how the legal-consent hook intercepts a sign-up request.
This diagram illustrates the before hook from the legal-consent plugin intercepting a sign-up request to perform validation.
Sources: packages/plugins/legal-consent/src/index.ts:197-224
Conclusion
Creating a plugin for Better Auth Kit is a structured process centered around the BetterAuthPlugin object. By defining a unique ID, a schema, API endpoints, and optional hooks, developers can create powerful, encapsulated, and reusable features. This approach ensures that new functionality integrates seamlessly into the broader auth system, from the database layer up to the API.