Skip to content

Schema Builder API Reference

The schema builder generates DDL SQL inside migration files. Import Schema and TMigration from @orphnet/d1-eloquent/cli. The up and down functions receive a Schema instance — no db argument, no exec call needed. SQL execution is handled by the CLI.

ts
import type { TMigration } from '@orphnet/d1-eloquent/cli'
import { Schema } from '@orphnet/d1-eloquent/cli'

Schema methods

schema.createTable(name, callback)

Creates a new table. Passes a TableBuilder to the callback for defining columns, constraints, and indexes.

ts
schema.createTable('posts', (t) => {
  t.id()
  t.text('title')
  t.timestamps()
})
// CREATE TABLE IF NOT EXISTS posts (id TEXT NOT NULL PRIMARY KEY, ...)

schema.dropTable(name)

Drops a table.

ts
schema.dropTable('posts')
// DROP TABLE IF EXISTS posts

schema.table(name, callback)

Alters an existing table. Use t.addText() or t.addInteger() inside the callback to add columns.

ts
schema.table('posts', (t) => {
  t.addText('slug', { nullable: true })
})
// ALTER TABLE posts ADD COLUMN slug TEXT

schema.raw(sql)

Appends a raw SQL statement as-is.

ts
schema.raw('CREATE INDEX idx_posts_slug ON posts (slug)')

TableBuilder — create mode

Methods available inside schema.createTable() callbacks.

t.id(name?)

Adds a TEXT NOT NULL PRIMARY KEY column. Defaults to "id".

ts
t.id()         // id TEXT NOT NULL PRIMARY KEY
t.id('uuid')   // uuid TEXT NOT NULL PRIMARY KEY

t.text(name, opts?)

ts
t.text(name: string, opts?: {
  nullable?: boolean   // default: false
  unique?: boolean     // default: false
  default?: string
}): this
ts
t.text('title')
t.text('email', { unique: true })
t.text('status', { default: 'draft' })
t.text('bio', { nullable: true })

t.integer(name, opts?)

ts
t.integer(name: string, opts?: {
  nullable?: boolean
  unique?: boolean
  default?: number
}): this
ts
t.integer('score')
t.integer('view_count', { default: 0 })

t.boolean(name, opts?)

Stored as INTEGER (0 / 1).

ts
t.boolean(name: string, opts?: {
  nullable?: boolean
  default?: boolean
}): this
ts
t.boolean('is_active', { default: true })

t.timestamps()

Adds created_at TEXT NOT NULL and updated_at TEXT NOT NULL.

ts
t.timestamps()

t.softDeletes(opts?)

Adds a nullable deleted_at TEXT column and (by default) an index on it.

ts
t.softDeletes(opts?: {
  column?: string    // default: 'deleted_at'
  index?: boolean    // default: true
  indexName?: string
}): this
ts
t.softDeletes()                          // deleted_at TEXT + index
t.softDeletes({ index: false })          // deleted_at TEXT, no index
t.softDeletes({ column: 'removed_at' })  // custom column name

t.primary(name)

Adds a PRIMARY KEY table constraint — use for composite primary keys.

ts
t.primary('user_id, role_id')
// PRIMARY KEY (user_id, role_id)

t.unique(name, indexName?)

Creates a CREATE UNIQUE INDEX statement after the table definition. Auto-names as uidx_<table>_<col> if indexName is omitted.

ts
t.unique('email')
// CREATE UNIQUE INDEX uidx_users_email ON users (email)

t.index(name, indexName?)

Creates a CREATE INDEX statement after the table definition. Auto-names as idx_<table>_<col>.

ts
t.index('user_id')
// CREATE INDEX idx_posts_user_id ON posts (user_id)

TableBuilder — alter mode

Methods available inside schema.table() callbacks.

t.addText(name, opts?)

ts
t.addText(name: string, opts?: {
  nullable?: boolean
  default?: string
}): this
ts
schema.table('posts', (t) => {
  t.addText('slug', { nullable: true })
})
// ALTER TABLE posts ADD COLUMN slug TEXT

t.addInteger(name, opts?)

ts
t.addInteger(name: string, opts?: {
  nullable?: boolean
  default?: number
}): this
ts
schema.table('posts', (t) => {
  t.addInteger('view_count', { default: 0 })
})
// ALTER TABLE posts ADD COLUMN view_count INTEGER NOT NULL DEFAULT 0

t.dropSoftDeletes(column?)

Emits a SQL comment. SQLite/D1 does not support DROP COLUMN safely — dropping deleted_at requires a table rebuild. Use this as a no-op placeholder in down().

ts
schema.table('posts', (t) => {
  t.dropSoftDeletes()
})
// -- NOTE: cannot DROP COLUMN deleted_at in SQLite/D1 safely

Complete migration example

ts
// src/database/migrations/20260301000000_create_posts_table.ts
import type { TMigration } from '@orphnet/d1-eloquent/cli'
import { Schema } from '@orphnet/d1-eloquent/cli'

const migration: TMigration = {
  name: '20260301000000_create_posts_table',

  up: (schema: Schema) => {
    schema.createTable('posts', (t) => {
      t.id()
      t.text('user_id')
      t.text('title')
      t.text('status', { default: 'draft' })
      t.text('body', { nullable: true })
      t.softDeletes()
      t.timestamps()
      t.index('user_id')
    })
  },

  down: (schema: Schema) => {
    schema.dropTable('posts')
  },
}

export default migration

Adding a column later

ts
// src/database/migrations/20260315000000_add_view_count_to_posts.ts
import type { TMigration } from '@orphnet/d1-eloquent/cli'
import { Schema } from '@orphnet/d1-eloquent/cli'

const migration: TMigration = {
  name: '20260315000000_add_view_count_to_posts',

  up: (schema: Schema) => {
    schema.table('posts', (t) => {
      t.addInteger('view_count', { default: 0 })
    })
  },

  down: (schema: Schema) => {
    // SQLite does not support DROP COLUMN — no-op
  },
}

export default migration

Released under the MIT License.