npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@surrealdb/better-auth

v0.1.0

Published

A [BetterAuth](https://better-auth.com) database adapter for [SurrealDB](https://surrealdb.com). It supports all BetterAuth plugins, schema generation, transactions, and the full set of WHERE operators.

Readme

@surrealdb/better-auth

A BetterAuth database adapter for SurrealDB. It supports all BetterAuth plugins, schema generation, transactions, and the full set of WHERE operators.

Prerequisites

  • SurrealDB 3.1+
  • BetterAuth 1.6.x
  • Node.js 18+ or Bun 1.x

Installation

# Bun
bun add @surrealdb/better-auth better-auth surrealdb

# npm
npm install @surrealdb/better-auth better-auth surrealdb

# pnpm
pnpm add @surrealdb/better-auth better-auth surrealdb

Quick Start

import { betterAuth } from 'better-auth';
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: 'namespace', database: 'database' });

export const auth = betterAuth({
    database: surrealAdapter({ db }),
    emailAndPassword: { enabled: true },
});

BetterAuth manages all table operations through the adapter.

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | db | Surreal | required | A connected SurrealDB client instance | | usePlural | boolean | false | Use plural table names (users instead of user) | | schemaMode | 'schemafull' \| 'schemaless' | 'schemafull' | Table mode used by generated schema. schemaless keeps known fields typed and indexed while accepting writes to undeclared fields |

surrealAdapter({
    db,
    usePlural: true, // use plural table names
})

Connecting to SurrealDB

The adapter accepts any connected Surreal instance. Connect before passing it:

import { Surreal } from 'surrealdb';

const db = new Surreal();

// WebSocket (recommended for persistent servers)
await db.connect('ws://localhost:8000/rpc', {
    namespace: 'myapp',
    database: 'production',
    authentication: {
        username: 'root',
        password: 'root',
    },
});

// HTTP (for stateless environments)
await db.connect('http://localhost:8000', {
    namespace: 'myapp',
    database: 'production',
});

Schema Generation

The adapter includes a createSchema implementation that generates SurrealQL DDL statements for all BetterAuth tables. Use the BetterAuth CLI to produce a schema.surql file:

bunx @better-auth/cli generate --output schema.surql

Then apply it to your SurrealDB instance:

surreal import --conn http://localhost:8000 \
    --ns myapp --db production \
    --user root --pass root \
    schema.surql

The generated schema uses SCHEMAFULL tables by default. Each field is typed from the BetterAuth schema: required fields take a concrete type (string, datetime, bool, and so on), optional fields use option<T | null> so they accept a typed value, a stored NULL, or a missing value, and object fields are marked FLEXIBLE so they hold arbitrary keys. DEFINE INDEX statements are emitted for unique fields and for fields BetterAuth marks as indexed. Every statement uses IF NOT EXISTS, so the file is safe to reapply.

If your app adds many dynamic plugin fields and you do not want to regenerate the schema each time, set schemaMode: 'schemaless'. Known fields stay typed and indexed, and writes to undeclared fields are accepted.

SurrealDB Helper Functions

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.

fn::auth::organization::*

These functions are emitted when the organization plugin is active.

| Function | Signature | Returns | Description | |---|---|---|---| | fn::auth::organization::member_of | (userId: string, organizationId: string) | bool | True if the user is a member of the organization | | 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) | bool | True if the user's role is equal to or senior to minRole (owner > admin > member) | | fn::auth::organization::members | (organizationId: string) | array | All members records for the organization | | fn::auth::organization::teams | (organizationId: string) | array | All teams records for the organization (requires teams: { enabled: true }) | | fn::auth::organization::has_permission | (userId: string, organizationId: string, resource: string, action: string) | bool | True if the user's role has a custom permission for the given resource and action (requires organizationRole table) |

Examples:

-- Check if a user belongs to an organization
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 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);

fn::auth::team::*

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

| Function | Signature | Returns | Description | |---|---|---|---| | fn::auth::team::member_of | (userId: string, teamId: string) | bool | True if the user is a member of the team | | fn::auth::team::members | (teamId: string) | array | All teamMembers records for the team |

Examples:

-- 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);

These functions are defined with IF NOT EXISTS, so re-running the generated schema is safe.

Transactions

Transaction support is built in. BetterAuth uses transactions internally for operations that need atomicity (for example, creating a session alongside a user record).

// BetterAuth handles this automatically.
// If you need manual transaction access:
const adapter = surrealAdapter({ db })(options);
await adapter.transaction(async (trx) => {
    await trx.create({ model: 'user', data: { ... } });
    await trx.create({ model: 'session', data: { ... } });
});

Transactions use SurrealDB.beginTransaction(). If any operation inside the callback throws, the transaction is cancelled and changes are discarded.

Using Plugins

The adapter works with all BetterAuth plugins. Pass them in the plugins array:

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(),
    ],
});

Each plugin may add new tables. Re-run schema generation after adding plugins to get the updated DDL.

SurrealDB Table Names

By default the adapter uses singular table names: user, session, verification, account. Set usePlural: true to use plural names instead.

BetterAuth lets you override model names per-table via the modelName option in your config. The adapter respects those overrides automatically.

Running Tests

The test suite starts an in-memory SurrealDB server, runs all BetterAuth adapter test suites against it, then shuts down.

# Requires the surreal CLI to be on your PATH
bun test

The surreal binary is available at https://surrealdb.com/install. On macOS with Homebrew:

brew install surrealdb/tap/surreal

License

Apache-2.0