Plugins
Plugins extend the server by adding endpoints, schema, hooks, middleware, rate limits, and context defaults.
Using A Plugin
auth = BetterAuth.auth(
secret: ENV.fetch("BETTER_AUTH_SECRET"),
plugins: [
BetterAuth::Plugins.magic_link(
send_magic_link: ->(data, _request) { AuthMailer.magic_link(data).deliver_later }
),
BetterAuth::Plugins.open_api
]
)Plugin endpoints become available on auth.api and through Rack.
auth.api.sign_in_magic_link(
body: {
email: "john@example.com",
callbackURL: "/dashboard"
}
)Creating A Plugin
my_plugin = BetterAuth::Plugin.new(
id: "my-plugin"
)What Can A Plugin Do?
A plugin can add endpoints, schema, migrations, hooks, middleware, request and response handlers, rate-limit rules, trusted origins, context defaults, client metadata, and error codes.
Create A Server Plugin
hello_plugin = BetterAuth::Plugin.new(
id: "hello",
endpoints: {
hello: BetterAuth::Endpoint.new(path: "/hello", method: "GET") do |ctx|
ctx.json({message: "hello"})
end
}
)Endpoints
Define endpoints with BetterAuth::Endpoint.new.
BetterAuth::Endpoint.new(path: "/profile", method: "GET") do |ctx|
session = BetterAuth::Routes.current_session(ctx)
ctx.json({user: session[:user]})
endEndpoint methods are normalized to API method names:
auth.api.helloSchema
Add fields or tables through plugin schema.
audit_plugin = BetterAuth::Plugin.new(
id: "audit",
schema: {
auditEvent: {
fields: {
userId: {type: "string", required: true},
action: {type: "string", required: true}
}
},
session: {
fields: {
activeAuditId: {type: "string", required: false}
}
}
}
)The migration generator and SQL helpers include plugin schema.
Hooks
Plugins can add before and after hooks.
BetterAuth::Plugin.new(
id: "company-email",
hooks: {
before: [
{
matcher: ->(ctx) { ctx.path == "/sign-up/email" },
handler: lambda do |ctx|
next if ctx.body["email"].to_s.end_with?("@company.com")
ctx.error("BAD_REQUEST", message: "Use your company email")
end
}
]
}
)Middleware
Plugin middleware runs before route-level request handlers.
BetterAuth::Plugin.new(
id: "header",
middlewares: [
{
path: "/hello",
middleware: lambda do |ctx|
ctx.headers["x-plugin"] = "header"
nil
end
}
]
)Return a value to short-circuit the endpoint.
On Request & On Response
On Request
on_request receives the Rack request and auth context.
BetterAuth::Plugin.new(
id: "request-id",
on_request: lambda do |request, context|
request.env["HTTP_X_REQUEST_ID"] ||= SecureRandom.uuid
{request: request}
end
)Return {response: [status, headers, body]} to stop request processing.
On Response
on_response receives the Rack response tuple.
BetterAuth::Plugin.new(
id: "response-header",
on_response: lambda do |response, context|
response[1]["x-auth-plugin"] = "response-header"
{response: response}
end
)Rate Limit
Plugins can contribute rate-limit rules.
BetterAuth::Plugin.new(
id: "limited-plugin",
rate_limit: [
{
path_matcher: ->(path) { path == "/hello" },
window: 60,
max: 5
}
]
)Trusted Origins
Plugins can return option defaults from init.
BetterAuth::Plugin.new(
id: "trusted-origin-plugin",
init: lambda do |_context|
{
options: {
trusted_origins: ["https://plugin.example.com"]
}
}
end
)Server-Plugin Helper Functions
getSessionFromCtx
Use BetterAuth::Routes.current_session(ctx).
session = BetterAuth::Routes.current_session(ctx)sessionMiddleware
Use endpoint use: middleware or plugin middleware.
require_session = lambda do |ctx|
BetterAuth::Routes.current_session(ctx)
nil
end
BetterAuth::Endpoint.new(path: "/private", method: "GET", use: [require_session]) do |ctx|
ctx.json({status: true})
endrequireResourceOwnership
Check ownership inside an endpoint or middleware.
owner = resource.user_id == BetterAuth::Routes.current_session(ctx)[:user]["id"]
return ctx.error("FORBIDDEN") unless ownerrequireOrgRole
Organization-specific role checks should use the organization plugin APIs and then return ctx.error("FORBIDDEN") when the current user lacks access.
Creating A Client Plugin
Ruby plugins are server plugins. Client-specific behavior should be implemented in the frontend that calls the HTTP routes.
Endpoint Interface
Expose server plugin endpoints over HTTP and document the request and response shape your frontend calls.
Get Actions
Frontend actions are regular HTTP calls to the mounted Ruby auth routes.
Get Atoms
State atoms belong in your frontend framework or state library.
Path Methods
Use the endpoint path and method declared by BetterAuth::Endpoint.new.
Fetch Plugins
Fetch middleware belongs in the frontend HTTP client.
Atom Listeners
Listen to frontend state changes in your frontend app and refetch Better Auth HTTP endpoints when needed.