A type-safe ORM for Cloudflare D1 — models, migrations, polymorphic relationships, JSON columns, FTS5 search, and revisions, all in one Workers-native package.
class User extends BaseModel<UserAttrs> {
static table = 'users'
static timestamps = true
static softDeletes = true
static relations = {
posts: { type: 'hasMany', model: () => Post }
}
}
const admins = await User.query()
.whereEq('role', 'admin')
.with(['posts'])
.paginate(1, 20)Models, queries, polymorphic relationships, migrations, soft deletes, revisions, KV cache, JSON columns — built in, working together, fully typed.
Full TypeScript inference with proxy attribute access, dirty tracking, lifecycle hooks, mass assignment protection.
WHERE, JOIN, HAVING, subqueries, unions, pagination, aggregates, FTS5, D1 sessions — all chainable, with correct SQL for OR groups, correlated whereHas, and empty whereIn.
belongsTo, hasMany, belongsToMany, plus full polymorphic (morphTo / morphMany / morphToMany / morphedByMany) with eager loading.
Fluent schema builder with single, comma-separated, or composite foreign keys. 8 CLI generators, seeders with faker, FK-safe fresh.
Automatic query scoping. Four revision modes with time-travel via asOf() and full create → update → delete audit chain.
KvCacheAdapter wraps a KVNamespace as a read-through cache with auto-invalidation on writes — opt-in per call.
First-class t.json() columns. whereJsonPath, whereJsonContains, selectJsonExtract, plus in-place json_set / json_patch updates.
Built for D1 prepared statements and Workers runtime. Auto-configuration via configure(env), multi-database routing.
Nine real patterns where d1-eloquent replaces a chunk of hand-rolled prepared statements with a typed one-liner. Same query, same result, less code.
Type-safe model replaces manual interfaces and three raw SQL prepared statements.
// Define your own types manually
interface User {
id: string
name: string
email: string
created_at: string
}
// Raw SQL for every operation
const { results } = await db
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id).all<User>()
const user = results[0]
if (!user) throw new Error('Not found')
// Manual update, manual SQL, again
await db
.prepare('UPDATE users SET email = ?, updated_at = ? WHERE id = ?')
.bind(newEmail, new Date().toISOString(), id)
.run()// Types are inferred from the model
class User extends BaseModel<UserAttrs> {
static table = 'users'
static timestamps = true
static softDeletes = true
}
// Type-safe, auto-scoped, throws if missing
const user = await User.findOrFail(id)
user.name // fully typed
user.email // fully typed
// Dirty tracking, one line
user.set('email', newEmail)
await user.save()Define a model, run a migration, query without writing SQL. Everything else — relationships, soft deletes, casts, hooks — sits in the same class declaration.
Extend BaseModel with your attributes.
class User extends BaseModel<UserAttrs> {
static table = 'users'
static timestamps = true
static softDeletes = true
static relations = {
posts: { type: 'hasMany', model: () => Post }
}
}Schema builder + CLI generators.
# Generate migration & model
$ bunx d1-eloquent make:model User
$ bunx d1-eloquent make:migration create_users
# Apply to D1
$ bunx d1-eloquent migrateFully typed, zero raw SQL.
const admins = await User.query()
.whereEq('role', 'admin')
.with(['posts'])
.orderBy('name')
.paginate(1, 20)
// { data: User[], total, lastPage, page, perPage } Scaffold resources, run migrations, seed deterministic fake data, and validate type/column drift — all from bunx d1-eloquent.
$ bunx d1-eloquent make:resource Post --soft-deletes Created migration: src/database/migrations/2026_05_17_create_posts.ts Created model: src/app/models/Post.ts Created factory: src/database/factories/PostFactory.ts Created seeder: src/database/seeders/PostSeeder.ts $ bunx d1-eloquent make:pivot post_tags Created migration: src/database/migrations/2026_05_17_post_tags.ts $ bunx d1-eloquent make:types --force Generating types for 4 model(s)... Generated: src/types/generated/UserAttrs.ts Generated: src/types/generated/PostAttrs.ts Generated: src/types/generated/TagAttrs.ts Generated: src/types/generated/index.ts
tinker.An instant REPL with your models already loaded, running against local D1 — no Worker, no boot wait. Query, mutate and inspect real data, sandboxed so nothing sticks unless you say so.
.commit to keep it..sql echoes SQL + bindings + timing; .explain the plan..table views, .schema, completion. A complete Hono on Cloudflare Workers handler that lists admin users — type-safe, auto-resolved D1 binding, ready to wrangler deploy.
import { Hono } from 'hono'
import { configure, BaseModel } from '@orphnet/d1-eloquent'
interface UserAttrs {
id: string
name: string
email: string
role: 'admin' | 'member'
}
class User extends BaseModel<UserAttrs> {
static table = 'users'
static timestamps = true
}
const app = new Hono<{ Bindings: { DB: D1Database } }>()
app.get('/admins', async (c) => {
configure(c.env)
const admins = await User.query()
.whereEq('role', 'admin')
.orderBy('name')
.get()
return c.json(admins.map(u => u.toJSON()))
})
export default appClick a pattern below — the right-hand pane shows the prepared statement and bindings the query builder will actually emit.
const users = await User.query() .whereEq('role', 'admin') .whereLike('name', 'A%') .orderBy('created_at', 'desc') .limit(10) .get()
SELECT * FROM users WHERE role = ? AND name LIKE ? ORDER BY created_at DESC LIMIT 10
['admin', 'A%']d1-eloquent against the alternatives — Raw D1 API, Drizzle, Laravel Eloquent — on every capability that matters when you're picking an ORM for the edge.
| Feature | D1Eloquent | Raw D1 API | Drizzle | Laravel |
|---|---|---|---|---|
| Type safety | ✓ | — | ✓ | — |
| Query builder | ✓ | — | ✓ | ✓ |
| Relationships | ✓ | — | ~ | ✓ |
| Polymorphic relations | ✓ | — | — | ✓ |
| Eager loading | ✓ | — | ✓ | ✓ |
| Soft deletes | ✓ | — | — | ✓ |
| Revision tracking | ✓ | — | — | ~ |
| Attribute casting | ✓ | — | — | ✓ |
| Collection API | ✓ | — | — | ✓ |
| Dirty tracking | ✓ | — | — | ✓ |
| Lifecycle hooks | ✓ | — | — | ✓ |
| Dynamic models | ✓ | — | — | — |
| Schema migrations | ✓ | — | ✓ | ✓ |
| Seeders & factories | ✓ | — | — | ✓ |
| Interactive REPL | ✓ | — | ~ | ✓ |
| Multi-database | ✓ | — | ✓ | ✓ |
| JSON queries | ✓ | — | ✓ | ✓ |
| KV cache adapter | ✓ | — | — | ~ |
| Pivot management | ✓ | — | — | ✓ |
| Relation aggregates | ✓ | — | — | ✓ |
| Cursor pagination | ✓ | — | ~ | ~ |
| FTS5 search | ✓ | ~ | — | — |
| D1 sessions | ✓ | ~ | — | — |
| RETURNING clauses | ✓ | ✓ | ✓ | ~ |
| Cloudflare D1 | ✓ | ✓ | ✓ | — |
| Edge runtime | ✓ | ✓ | ✓ | — |
d1-eloquent is in alpha. Drop your email if you want to know when it ships stable, plus follow-up notes on breaking-change pre-releases.
Install, run a migration, and write your first type-safe query against Cloudflare D1 — in under five minutes.