Skip to main content
Beta

This feature is in beta. Core behavior is stable and ready to try, but some APIs or configuration may still evolve 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