Skip to main content
Beta

This feature is in beta. Core behavior is stable, but some APIs or configuration may change before general availability.

Database

JavaScriptDartSwiftKotlinJavaScalaPythonGoPHPRustC#C++RubyElixir

EdgeBase Database defaults to SQLite on D1 and Durable Objects, but single-instance DB blocks can also switch to Neon or PostgreSQL through Hyperdrive-backed bindings. You get CRUD, queries, full-text search, access rules, schema validation, and automatic migrations without managing a separate data layer by hand.


Control Surface
  • Access Rules: Database Access Rules gate table reads, writes, deletes, and DB block access.
  • Hooks: Table Hooks validate writes, enrich reads, and run inline side effects.
  • Triggers: Database Triggers run App Function handlers after insert, update, or delete events.
  • Handlers: Inline database behavior lives under databases[namespace].tables[name].handlers.hooks in edgebase.config.ts.

Key Features

📝

CRUD + Batch

Insert, update, delete, upsert — single or batch

🔍

Queries

Filter (where), sort (orderBy), paginate (offset or cursor-based), OR conditions

Schema Validation

Declarative schema with type checking, required fields, min/max constraints

🔎

Full-Text Search

FTS5 with trigram tokenizer — works with CJK and all languages

Subscriptions

onSnapshot subscriptions with server-side filtering and automatic delta sync

🔒

Access Rules

Deny-by-default, per-operation rules with auth and resource context

🪝

Table Hooks

Validate writes, enrich reads, and run inline side effects inside the active DB backend

⚙️

Database Triggers

Run server-side code automatically on insert, update, or delete

🔄

Migrations

Lazy migration engine — automatic for additions, explicit SQL for destructive changes

🏢

Multi-Tenancy

DB blocks — single-instance or dynamic (per-ID isolation) with any name you choose

DB Blocks

EdgeBase uses DB blocks to route SQLite storage. There are two types — single-instance (one DB, no instance ID) and dynamic (instance: true, one DB per ID). The block name is a config key you choose freely; the examples below use app, user, and workspace, but any name works:

client.db('app')                       // One single-instance DB block (D1 by default)
client.db('user', userId) // Per-user isolated DB block
client.db('workspace', 'ws-456') // Per-workspace isolated DB block

Each DB block is its own backing database. Tables inside the same block can JOIN each other because they share one storage engine. Single-instance blocks default to D1 for globally shared data, can explicitly use Durable Objects for single-instance SQLite, or can switch to Neon/PostgreSQL when you want centralized PostgreSQL semantics. Dynamic blocks scale out on Durable Objects — one isolated SQLite per instance ID.

If you're working locally, you can create the DB block from the Admin Dashboard at Database -> Tables -> + DB, or define it manually in config. See Create Database, Defining Tables, and Admin Dashboard Schema Editor.

Quick Example

// Define schema
app: {
tables: {
posts: {
schema: {
title: { type: 'string', required: true, max: 200 },
content: { type: 'text' },
status: { type: 'string', default: 'draft' },
},
access: {
read() { return true },
insert(auth) { return auth !== null },
update(auth, row) { return auth !== null && auth.id === row.authorId },
delete(auth) { return auth !== null && auth.role === 'admin' },
},
},
},
}

Assume client is already initialized with your platform SDK.

const post = await client.db('app').table('posts').insert({
title: 'Hello World',
content: 'My first post.',
});
Admin SDK Coverage

Server-side database operations, raw SQL, and DB block access are available across all Admin SDKs.

Next Steps