diff --git a/docs/content/docs/concepts/plugins.mdx b/docs/content/docs/concepts/plugins.mdx
index 5cd403cd78..e21af76b17 100644
--- a/docs/content/docs/concepts/plugins.mdx
+++ b/docs/content/docs/concepts/plugins.mdx
@@ -37,17 +37,41 @@ const authClient = createAuthClient({
## Creating a Plugin
-To create a plugin you need to pass an object that satisfies the `BetterAuthPlugin` interface.
+To get started, you'll need a server plugin.
+Server plugins are the backbone of all plugins, and client plugins are there to provide an interface with frontend APIs to easily work with your server plugins.
+
+
+ If your server plugins has endpoints that needs to be called from the client, you'll also need to create a client plugin.
+
+
+### What can a plugin do?
+
+* Create custom `endpoint`s to perform any action you want.
+* Extend database tables with custom `schemas`.
+* Use a `middleware` to target a group of routes using it's route matcher, and run only when those routes are called through a request.
+* Use `hooks` to target a specific route or request. And if you want to run the hook even if the endpoint is called directly.
+* Use `onRequest` or `onResponse` if you want to do something that affects all requests or responses.
+* Create custom `rate-limit` rule.
+
+## Create a Server plugin
+
+To create a server plugin you need to pass an object that satisfies the `BetterAuthPlugin` interface.
The only required property is `id`, which is a unique identifier for the plugin.
+Both server and client plugins can use the same `id`.
```ts title="plugin.ts"
import type { BetterAuthPlugin } from "better-auth";
-const myPlugin = {
- id: "my-plugin",
-} satisfies BetterAuthPlugin
+export const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ } satisfies BetterAuthPlugin
+}
```
+
+ You don't have to make the plugin a function, but it's recommended to do so. This way you can pass options to the plugin and it's consistent with the built-in plugins.
+
### Endpoints
@@ -60,19 +84,23 @@ Better Auth uses wraps around another library called {
-
- })
- }
+const myPlugin = ()=> {
+ return {
+ id: "my-plugin",
+ endpoints: {
+ getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
+ method: "GET",
+ }, async(ctx) => {
+ return ctx.json({
+ message: "Hello World"
+ })
+ })
+ }
+ } satisfies BetterAuthPlugin
}
```
-Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `ctx` object, it'll provide you with an object called `context` that give you access better-auth specific contexts including `options`, `db`, `baseURL` and more.
+Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `ctx` object, it'll provide another object called `context` that give you access better-auth specific contexts including `options`, `db`, `baseURL` and more.
**Context Object**
@@ -164,22 +192,24 @@ const myPlugin = (opts: PluginOptions)=>{
}
```
-if you add additional fields to a `user` or `session` table, the types will be inferred automatically.
+if you add additional fields to a `user` or `session` table, the types will be inferred automatically on `getSession` and `signUpEmail` calls.
```ts title="plugin.ts"
-const myPlugin = {
- id: "my-plugin",
- schema: {
- user: {
- fields: {
- age: {
- type: "number",
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ schema: {
+ user: {
+ fields: {
+ age: {
+ type: "number",
+ },
},
},
},
- },
-} satisfies BetterAuthPlugin
+ } satisfies BetterAuthPlugin
+}
```
This will add an `age` field to the `user` table and all `user` returning endpoints will include the `age` field and it'll be inferred properly by typescript.
@@ -195,32 +225,34 @@ Hooks are used to run code before or after an action is performed, either from a
```ts title="plugin.ts"
import { createAuthMiddleware } from "better-auth/plugins";
-const myPlugin = {
- id: "my-plugin",
- hooks: {
- before: [{
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ hooks: {
+ before: [{
+ matcher: (context)=>{
+ return context.headers.get("x-my-header") === "my-value"
+ },
+ handler: createAuthMiddleware(async(ctx)=>{
+ //do something before the request
+ return {
+ context: ctx // if you want to modify the context
+ }
+ })
+ }],
+ after: [{
matcher: (context)=>{
- return context.headers.get("x-my-header") === "my-value"
+ return context.path === "/sign-up/email"
},
- handler: createAuthMiddleware(async(ctx)=>{
- //do something before the request
- return {
- context: ctx // if you want to modify the context
- }
- })
- }],
- after: [{
- matcher: (context)=>{
- return context.req.method === "POST"
- },
- handler: async(ctx)=>{
- return ctx.json({
- message: "Hello World"
- }) // if you want to modify the response
- }
- }]
- }
-} satisfies BetterAuthPlugin
+ handler: async(ctx)=>{
+ return ctx.json({
+ message: "Hello World"
+ }) // if you want to modify the response
+ }
+ }]
+ }
+ } satisfies BetterAuthPlugin
+}
```
### Middleware
@@ -229,25 +261,28 @@ You can add middleware to the server by passing a `middleware` array. This array
The `path` can be either a string or a path matcher, using the same path-matching system as `better-call`.
+If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client.
+
```ts title="plugin.ts"
-const myPlugin = {
- id: "my-plugin",
- middleware: [
- {
- path: "/my-plugin/hello-world",
- middleware: createAuthMiddleware(async(ctx)=>{
- //do something
- })
- }
- ]
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ middleware: [
+ {
+ path: "/my-plugin/hello-world",
+ middleware: createAuthMiddleware(async(ctx)=>{
+ //do something
+ })
+ }
+ ]
+ } satisfies BetterAuthPlugin
}
```
-If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client.
### On Request & On Response
-Additional to middlewares you can also hook into right before a request is made and right after a response is returned. This is mostly useful if you want to do something that affects all requests or responses.
+Additional to middlewares, you can also hook into right before a request is made and right after a response is returned. This is mostly useful if you want to do something that affects all requests or responses.
#### On Request
@@ -260,11 +295,13 @@ Here’s how it works:
- **Modify the Request**: You can also return a modified `request` object to change the request before it's sent.
```ts title="plugin.ts"
-const myPlugin = {
- id: "my-plugin",
- onRequest: async (request, context) => {
- //do something
- },
+const myPlugin = ()=> {
+ return {
+ id: "my-plugin",
+ onRequest: async (request, context) => {
+ //do something
+ },
+ } satisfies BetterAuthPlugin
}
```
@@ -278,46 +315,109 @@ Here’s how to use it:
- **Continue Normally**: If you don’t return anything, the response will be sent as is.
```ts title="plugin.ts"
-const myPlugin = {
- id: "my-plugin",
- onResponse: async (response, context) => {
- //do something
- },
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ onResponse: async (response, context) => {
+ //do something
+ },
+ } satisfies BetterAuthPlugin
}
```
-
-#### When to use Middleware, Hooks, On Request, On Response
-
-- **Hooks**: You should use hooks when you want to target a specific route or request. And you want to run the hook even if the endpoint is called directly.
-- **Middlewares**: You should use middlewares when you want to use a route matcher to target a group of routes. And you want to run the middleware only when the routes are called through a request.
-- **On Request & On Response**: You should use on request and on response when you want to do something that affects all requests or responses.
-
### Rate Limit
You can define custom rate limit rules for your plugin by passing a `rateLimit` array. The rate limit array should contain an array of rate limit objects.
```ts title="plugin.ts"
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ rateLimit: [
+ {
+ pathMatcher: (path)=>{
+ return path === "/my-plugin/hello-world"
+ },
+ limit: 10,
+ window: 60,
+ }
+ ]
+ } satisfies BetterAuthPlugin
+}
+```
+
+### Server-plugin helper functions
+
+Some additional helper functions for creating server plugins.
+
+#### `getSessionFromCtx`
+
+Allows you to get the client's session data by passing the auth middleware's `context`.
+
+```ts title="plugin.ts"
+import { createAuthMiddleware } from "better-auth/plugins";
+
const myPlugin = {
id: "my-plugin",
- rateLimit: [
- {
- pathMatcher: (path)=>{
- return path === "/my-plugin/hello-world"
- },
- limit: 10,
- window: 60,
+ hooks: {
+ before: [{
+ matcher: (context)=>{
+ return context.headers.get("x-my-header") === "my-value"
+ },
+ handler: createAuthMiddleware(async (ctx) => {
+ const session = await getSessionFromCtx(ctx);
+ //do something with the client's session.
+
+ return {
+ context: ctx
+ }
+ })
+ }],
+ }
+} satisfies BetterAuthPlugin
+```
+
+#### `sessionMiddleware`
+
+A middleware that checks if the client has a valid session. If the client has a valid session, it'll add the session data to the context object.
+
+```ts title="plugin.ts"
+import { createAuthMiddleware, sessionMiddleware } from "better-auth/plugins";
+
+const myPlugin = ()=>{
+ return {
+ id: "my-plugin",
+ endpoints: {
+ getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
+ method: "GET",
+ use: [sessionMiddleware], //[!code highlight]
+ }, async(ctx) => {
+ const session = ctx.context.session;
+ return ctx.json({
+ message: "Hello World"
+ })
+ })
}
- ]
+ } satisfies BetterAuthPlugin
}
```
-### Client
+## Creating a client plugin
If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better Auth clients can infer the endpoints from the server plugins. You can also add additional client side logic.
-**Endpoint Inference**
+```ts title="client-plugin.ts"
+import type { BetterAuthClientPlugin } from "better-auth";
+
+export const myPluginClient = ()=>{
+ return {
+ id: "my-plugin",
+ } satisfies BetterAuthClientPlugin
+}
+```
+
+### Endpoint Interface
Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin.
@@ -325,15 +425,17 @@ The client infers the `path` as an object and converts kebab-case to camelCase.
```ts title="client-plugin.ts"
import type { BetterAuthClientPlugin } from "better-auth/client";
-import type { myPlugin } from ".//plugin";
+import type { myPlugin } from "./plugin";
-const myPluginClient = {
- id: "my-plugin",
- $InferServerPlugin: {} as ReturnType,
-} satisfies BetterAuthClientPlugin
+const myPluginClient = ()=> {
+ return {
+ id: "my-plugin",
+ $InferServerPlugin: {} as ReturnType,
+ } satisfies BetterAuthClientPlugin
+}
```
-**getActions**
+### Get actions
If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client.
@@ -372,7 +474,7 @@ As a general guideline, ensure that each function accepts only one argument, wit
If your use case involves actions beyond API calls, feel free to deviate from this rule.
-**getAtoms**
+### Get Atoms
This is only useful if you want to provide `hooks` like `useSession`.
@@ -396,7 +498,7 @@ const myPluginClient = {
See built in plugins for examples of how to use atoms properly.
-**pathMethods**
+### Path methods
by default, inferred paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET").
@@ -413,11 +515,11 @@ const myPluginClient = {
} satisfies BetterAuthClientPlugin
```
-**fetchPlugins**
+### Fetch plugins
If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the better fetch documentation.
-**atomListeners**
+### Atom Listeners
This is only useful if you want to provide `hooks` like `useSession` and you want to listen to atoms and re-evaluate them when they change.