Skip to content

D1 Adapter

The D1 adapter is the primary storage backend for Workers Auth. It persists users, sessions, and role assignments to a Cloudflare D1 SQLite database. Tables are created automatically on first use.

Terminal window
wrangler d1 create auth
[[d1_databases]]
binding = "DB"
database_name = "auth"
database_id = "your-database-id"
import { D1Adapter } from 'workers-auth/adapters/d1';
const auth = WorkersAuth({
database: (binding) => D1Adapter(binding),
// ...
});

The database config takes a factory function. At request time, Workers Auth passes the DB binding from your Worker’s env object.

On the first call to initialize(), the adapter creates the following tables if they don’t exist:

CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
expires_at INTEGER NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE TABLE IF NOT EXISTS user_roles (
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (user_id, role)
);

No manual migration step is needed. Deploy and the tables are created on the first request.

The D1 adapter implements the DatabaseAdapter interface:

interface DatabaseAdapter {
initialize(): Promise<void>;
findUserByEmail(email: string): Promise<User | null>;
findUserById(id: string): Promise<User | null>;
createUser(email: string): Promise<User>;
createSession(userId: string, expiresAt: number): Promise<Session>;
getSession(sessionId: string): Promise<Session | null>;
deleteSession(sessionId: string): Promise<void>;
deleteUserSessions(userId: string): Promise<string[]>;
listUsers(limit: number, offset: number): Promise<{ users: User[]; total: number }>;
updateUserStatus(userId: string, status: 'active' | 'banned'): Promise<void>;
getUserRoles(userId: string): Promise<string[]>;
setUserRoles(userId: string, roles: string[]): Promise<void>;
addUserRole(userId: string, role: string): Promise<void>;
removeUserRole(userId: string, role: string): Promise<void>;
}

To use a different database, implement DatabaseAdapter:

import type { DatabaseAdapter } from 'workers-auth';
function MyCustomAdapter(binding: any): DatabaseAdapter {
return {
async initialize() {
// Create tables or run migrations
},
async findUserByEmail(email) {
// Return User or null
},
async findUserById(id) {
// Return User or null
},
async createUser(email) {
// Insert and return User (with status: 'active')
},
async createSession(userId, expiresAt) {
// Insert and return Session
},
async getSession(sessionId) {
// Return Session (if not expired) or null
},
async deleteSession(sessionId) {
// Delete single session
},
async deleteUserSessions(userId) {
// Delete all sessions for user, return deleted IDs
},
async listUsers(limit, offset) {
// Return { users: User[], total: number }
},
async updateUserStatus(userId, status) {
// Update user status to 'active' or 'banned'
},
async getUserRoles(userId) {
// Return array of role names
},
async setUserRoles(userId, roles) {
// Replace all roles for user
},
async addUserRole(userId, role) {
// Add a single role to user
},
async removeUserRole(userId, role) {
// Remove a single role from user
},
};
}

Then pass it to WorkersAuth:

const auth = WorkersAuth({
database: (binding) => MyCustomAdapter(binding),
// ...
});