Get the FREE Ultimate OpenClaw Setup Guide →

d1-drizzle-schema

npx machina-cli add skill jezweb/claude-skills/d1-drizzle-schema --openclaw
Files (1)
SKILL.md
5.3 KB

D1 Drizzle Schema

Generate correct Drizzle ORM schemas for Cloudflare D1. D1 is SQLite-based but has important differences that cause subtle bugs if you use standard SQLite patterns. This skill produces schemas that work correctly with D1's constraints.

Critical D1 Differences

FeatureStandard SQLiteD1
Foreign keysOFF by defaultAlways ON (cannot disable)
Boolean typeNoNo — use integer({ mode: 'boolean' })
Datetime typeNoNo — use integer({ mode: 'timestamp' })
Max bound params~999100 (affects bulk inserts)
JSON supportExtensionAlways available (json_extract, ->, ->>)
ConcurrencyMulti-writerSingle-threaded (one query at a time)

Workflow

Step 1: Describe the Data Model

Gather requirements: what tables, what relationships, what needs indexing. If working from an existing description, infer the schema directly.

Step 2: Generate Drizzle Schema

Create schema files using D1-correct column patterns:

import { sqliteTable, text, integer, real, index, uniqueIndex } from 'drizzle-orm/sqlite-core'

export const users = sqliteTable('users', {
  // UUID primary key (preferred for D1)
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),

  // Text fields
  name: text('name').notNull(),
  email: text('email').notNull(),

  // Enum (stored as TEXT, validated at schema level)
  role: text('role', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('viewer'),

  // Boolean (D1 has no BOOL — stored as INTEGER 0/1)
  emailVerified: integer('email_verified', { mode: 'boolean' }).notNull().default(false),

  // Timestamp (D1 has no DATETIME — stored as unix seconds)
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),

  // Typed JSON (stored as TEXT, Drizzle auto-serialises)
  preferences: text('preferences', { mode: 'json' }).$type<UserPreferences>(),

  // Foreign key (always enforced in D1)
  organisationId: text('organisation_id').references(() => organisations.id, { onDelete: 'cascade' }),
}, (table) => ({
  emailIdx: uniqueIndex('users_email_idx').on(table.email),
  orgIdx: index('users_org_idx').on(table.organisationId),
}))

See references/column-patterns.md for the full type reference.

Step 3: Add Relations

Drizzle relations are query builder helpers (separate from FK constraints):

import { relations } from 'drizzle-orm'

export const usersRelations = relations(users, ({ one, many }) => ({
  organisation: one(organisations, {
    fields: [users.organisationId],
    references: [organisations.id],
  }),
  posts: many(posts),
}))

Step 4: Export Types

export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert

Step 5: Set Up Drizzle Config

Copy assets/drizzle-config-template.ts to drizzle.config.ts and update the schema path.

Step 6: Add Migration Scripts

Add to package.json:

{
  "db:generate": "drizzle-kit generate",
  "db:migrate:local": "wrangler d1 migrations apply DB --local",
  "db:migrate:remote": "wrangler d1 migrations apply DB --remote"
}

Always run on BOTH local AND remote before testing.

Step 7: Generate DATABASE_SCHEMA.md

Document the schema for future sessions:

  • Tables with columns, types, and constraints
  • Relationships and foreign keys
  • Indexes and their purpose
  • Migration workflow

Bulk Insert Pattern

D1 limits bound parameters to 100. Calculate batch size:

const BATCH_SIZE = Math.floor(100 / COLUMNS_PER_ROW)
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
  await db.insert(table).values(rows.slice(i, i + BATCH_SIZE))
}

D1 Runtime Usage

import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema'

// In Worker fetch handler:
const db = drizzle(env.DB, { schema })

// Query patterns
const all = await db.select().from(schema.users).all()           // Array<User>
const one = await db.select().from(schema.users).where(eq(schema.users.id, id)).get()  // User | undefined
const count = await db.select({ count: sql`count(*)` }).from(schema.users).get()

Reference Files

WhenRead
D1 vs SQLite, JSON queries, limitsreferences/d1-specifics.md
Column type patterns for Drizzle + D1references/column-patterns.md

Assets

FilePurpose
assets/drizzle-config-template.tsStarter drizzle.config.ts for D1
assets/schema-template.tsExample schema with all common D1 patterns

Source

git clone https://github.com/jezweb/claude-skills/blob/main/plugins/cloudflare/skills/d1-drizzle-schema/SKILL.mdView on GitHub

Overview

D1 Drizzle Schema generates Drizzle ORM schemas tailored for Cloudflare D1, a SQLite-based engine with unique constraints. It outputs schema files, migrations, type exports, and DATABASE_SCHEMA.md while respecting D1 quirks like always-on foreign keys, 100 parameter limit, and JSON stored as TEXT.

How This Skill Works

Begin by describing the data model, then generate a D1-correct Drizzle schema using patterns such as booleans and timestamps stored as integers, and JSON stored as TEXT. Add relations, export types, configure drizzle, create migration scripts, and finally generate DATABASE_SCHEMA.md documentation to capture the schema.

When to Use It

  • When creating a new Cloudflare D1 database and you need a correct Drizzle schema from the start
  • When adding tables or evolving an existing D1 schema with proper D1 patterns
  • When scaffolding a D1 data layer for a new project or service
  • When generating migrations for bulk inserts under D1's 100 parameter limit
  • When producing DATABASE_SCHEMA.md documentation alongside the schema and migrations

Quick Start

  1. Step 1: Describe the data model and relationships (tables, keys, indexes, and constraints).
  2. Step 2: Generate a D1-correct Drizzle schema using D1 patterns (text for strings, integer booleans, integer timestamps, and text for JSON).
  3. Step 3: Add migrations, configure drizzle.config.ts, and generate DATABASE_SCHEMA.md for documentation

Best Practices

  • Design schemas with D1 quirks in mind: use integer({ mode: 'boolean' }) for booleans and integer({ mode: 'timestamp' }) for datetimes
  • Store JSON as TEXT and rely on Drizzle's json handling, avoiding native JSON types
  • Always enable and rely on foreign keys in D1 (enforced by the engine)
  • Plan migrations with local and remote (Wrangler D1) deployment in mind
  • Generate and maintain DATABASE_SCHEMA.md to document the D1 schema alongside code

Example Use Cases

  • Scaffolding a new user-service schema for a Cloudflare D1 database with proper foreign keys and JSON fields
  • Adding a posts table to an existing D1 database while honoring 100-parameter bulk insert limits
  • Exporting types for User and NewUser and wiring up drizzle config for D1 integration
  • Creating migration scripts and ensuring local and remote tests run before deploy
  • Generating DATABASE_SCHEMA.md to accompany the generated Drizzle schema

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers