• Start

Authentication

/

Better Auth

Plugins

Use any Better Auth plugin with the SurrealDB adapter and call the SurrealQL helper functions generated for the organisation plugin.

The @surrealdb/better-auth adapter works with all Better Auth plugins. Pass them in the plugins array of your Better Auth configuration:

import { betterAuth } from 'better-auth';
import { organization } from 'better-auth/plugins/organization';
import { twoFactor } from 'better-auth/plugins/two-factor';
import { admin } from 'better-auth/plugins/admin';
import { Surreal } from 'surrealdb';
import { surrealAdapter } from '@surrealdb/better-auth';

const db = new Surreal();
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'myapp', database: 'production' });

export const auth = betterAuth({
database: surrealAdapter({ db }),
plugins: [
organization(),
twoFactor(),
admin(),
],
});

A wider example combining several plugins:

import { betterAuth } from 'better-auth';
import { admin } from 'better-auth/plugins/admin';
import { anonymous } from 'better-auth/plugins/anonymous';
import { bearer } from 'better-auth/plugins/bearer';
import { magicLink } from 'better-auth/plugins/magic-link';
import { multiSession } from 'better-auth/plugins/multi-session';
import { organization } from 'better-auth/plugins/organization';
import { twoFactor } from 'better-auth/plugins/two-factor';
import { username } from 'better-auth/plugins/username';
import { Surreal } from 'surrealdb';
import { surrealAdapter } from '@surrealdb/better-auth';

const db = new Surreal();
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'myapp', database: 'production' });

export const auth = betterAuth({
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000',
emailAndPassword: { enabled: true },
database: surrealAdapter({ db }),
plugins: [
organization({
teams: { enabled: true },
dynamicAccessControl: { enabled: true },
}),
twoFactor(),
admin(),
bearer(),
username(),
magicLink({
sendMagicLink: async ({ email, url }) => {
// send an email containing the magic link url
},
}),
anonymous(),
multiSession(),
],
});

When you use the organization plugin (with or without the teams option), schema generation also emits a set of fn::* SurrealQL functions you can call directly in your own queries, rules, and permissions. These functions are defined with IF NOT EXISTS, so re-running the generated schema is safe.

These functions are emitted when the organization plugin is active.

FunctionSignatureReturnsDescription
fn::auth::organization::member_of(userId: string, organizationId: string)boolTrue if the user is a member of the organisation.
fn::auth::organization::get_role(userId: string, organizationId: string)option<string>The member's role ("owner", "admin", "member"), or NONE if not a member.
fn::auth::organization::has_role(userId: string, organizationId: string, minRole: string)boolTrue if the user's role is equal to or senior to minRole (owner > admin > member).
fn::auth::organization::members(organizationId: string)arrayAll members records for the organisation.
fn::auth::organization::teams(organizationId: string)arrayAll teams records for the organisation (requires teams: { enabled: true }).
fn::auth::organization::has_permission(userId: string, organizationId: string, resource: string, action: string)boolTrue if the user's role has a custom permission for the given resource and action (requires dynamicAccessControl).
-- Check if a user belongs to an organisation
IF fn::auth::organization::member_of($userId, $organizationId) {
-- allow access
};

-- Require at least admin-level access
IF fn::auth::organization::has_role($userId, $organizationId, "admin") {
-- perform a privileged operation
};

-- Get a user's role string
LET $role = fn::auth::organization::get_role($userId, $organizationId);

-- List all members
LET $members = fn::auth::organization::members($organizationId);

These functions are emitted when teams: { enabled: true } is set on the organization plugin.

FunctionSignatureReturnsDescription
fn::auth::team::member_of(userId: string, teamId: string)boolTrue if the user is a member of the team.
fn::auth::team::members(teamId: string)arrayAll teamMembers records for the team.
-- Check team membership
IF fn::auth::team::member_of($userId, $teamId) {
-- allow team-scoped access
};

-- List all team members
LET $members = fn::auth::team::members($teamId);

Was this page helpful?