Build type-safe D1 databases with Drizzle ORM. Includes schema definition, migrations with Drizzle Kit, relations, and D1 batch API patterns. Prevents 12 errors including SQL BEGIN failures and foreign key issues. Use when: defining D1 schemas, managing migrations, or troubleshooting D1_ERROR, BEGIN TRANSACTION, foreign keys.
/plugin marketplace add jezweb/claude-skills/plugin install jezweb-tooling-skills@jezweb/claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdagents/drizzle-migrate.mdcommands/init.mdreferences/common-errors.mdreferences/links-to-official-docs.mdreferences/migration-workflow.mdreferences/query-builder-api.mdreferences/schema-patterns.mdreferences/wrangler-setup.mdrules/drizzle-orm-d1.mdscripts/check-versions.shtemplates/basic-queries.tstemplates/client.tstemplates/cloudflare-worker-integration.tstemplates/drizzle.config.tstemplates/migrations/0001_example.sqltemplates/package.jsontemplates/prepared-statements.tstemplates/relations-queries.tstemplates/schema.tsStatus: Production Ready ✅ Last Updated: 2026-01-09 Latest Version: drizzle-orm@0.45.1, drizzle-kit@0.31.8, better-sqlite3@12.5.0 Dependencies: cloudflare-d1, cloudflare-worker-base
# 1. Install
npm install drizzle-orm
npm install -D drizzle-kit
# 2. Configure drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './migrations',
dialect: 'sqlite',
driver: 'd1-http',
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
});
# 3. Configure wrangler.jsonc
{
"d1_databases": [{
"binding": "DB",
"database_name": "my-database",
"database_id": "your-database-id",
"migrations_dir": "./migrations" // CRITICAL: Points to Drizzle migrations
}]
}
# 4. Define schema (src/db/schema.ts)
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
email: text('email').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
});
# 5. Generate & apply migrations
npx drizzle-kit generate
npx wrangler d1 migrations apply my-database --local # Test first
npx wrangler d1 migrations apply my-database --remote # Then production
# 6. Query in Worker
import { drizzle } from 'drizzle-orm/d1';
import { users } from './db/schema';
const db = drizzle(env.DB);
const allUsers = await db.select().from(users).all();
✅ Use db.batch() for transactions - D1 doesn't support SQL BEGIN/COMMIT (see Issue #1)
✅ Test migrations locally first - Always --local before --remote
✅ Use integer with mode: 'timestamp' for dates - D1 has no native date type
✅ Use .$defaultFn() for dynamic defaults - Not .default() for functions
✅ Set migrations_dir in wrangler.jsonc - Points to ./migrations
❌ Never use SQL BEGIN TRANSACTION - D1 requires batch API
❌ Never use drizzle-kit push for production - Use generate + apply
❌ Never mix wrangler.toml and wrangler.jsonc - Use wrangler.jsonc only
npx drizzle-kit studio
# Opens http://local.drizzle.studio
# For remote D1 database
npx drizzle-kit studio --port 3001
Features:
| Command | Purpose |
|---|---|
drizzle-kit generate | Generate SQL migrations from schema changes |
drizzle-kit push | Push schema directly (dev only, not for production) |
drizzle-kit pull | Introspect existing database → Drizzle schema |
drizzle-kit check | Validate migration integrity (race conditions) |
drizzle-kit up | Upgrade migration snapshots to latest format |
# Introspect existing D1 database
npx drizzle-kit pull
# Validate migrations haven't collided
npx drizzle-kit check
Build queries conditionally with .$dynamic():
import { eq, and, or, like, sql } from 'drizzle-orm';
// Base query
function getUsers(filters: { name?: string; email?: string; active?: boolean }) {
let query = db.select().from(users).$dynamic();
if (filters.name) {
query = query.where(like(users.name, `%${filters.name}%`));
}
if (filters.email) {
query = query.where(eq(users.email, filters.email));
}
if (filters.active !== undefined) {
query = query.where(eq(users.active, filters.active));
}
return query;
}
// Usage
const results = await getUsers({ name: 'John', active: true });
import { users } from './schema';
// Insert or ignore if exists
await db.insert(users)
.values({ id: 1, email: 'test@example.com', name: 'Test' })
.onConflictDoNothing();
// Insert or update specific fields on conflict
await db.insert(users)
.values({ id: 1, email: 'test@example.com', name: 'Test' })
.onConflictDoUpdate({
target: users.email, // Conflict on unique email
set: {
name: sql`excluded.name`, // Use value from INSERT
updatedAt: new Date(),
},
});
⚠️ D1 Upsert Caveat: Target must be a unique column or primary key.
import { drizzle } from 'drizzle-orm/d1';
// Enable query logging
const db = drizzle(env.DB, { logger: true });
// Custom logger
const db = drizzle(env.DB, {
logger: {
logQuery(query, params) {
console.log('SQL:', query);
console.log('Params:', params);
},
},
});
// Get SQL without executing (for debugging)
const query = db.select().from(users).where(eq(users.id, 1));
const sql = query.toSQL();
console.log(sql.sql, sql.params);
This skill prevents 12 documented issues:
Error: D1_ERROR: Cannot use BEGIN TRANSACTION
Source: https://github.com/drizzle-team/drizzle-orm/issues/4212
Why: Drizzle uses SQL BEGIN TRANSACTION, but D1 requires batch API instead.
Prevention: Use db.batch([...]) instead of db.transaction()
Error: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT
Source: https://github.com/drizzle-team/drizzle-orm/issues/4089
Why: Drizzle uses PRAGMA foreign_keys = OFF; which causes migration failures.
Prevention: Define foreign keys with cascading: .references(() => users.id, { onDelete: 'cascade' })
Error: Error: No such module "wrangler"
Source: https://github.com/drizzle-team/drizzle-orm/issues/4257
Why: Importing from wrangler package in runtime code fails in production.
Prevention: Use import { drizzle } from 'drizzle-orm/d1', never import from wrangler
Error: TypeError: Cannot read property 'prepare' of undefined
Why: Binding name in code doesn't match wrangler.jsonc configuration.
Prevention: Ensure "binding": "DB" in wrangler.jsonc matches env.DB in code
Error: Migration failed to apply: near "...": syntax error
Why: Syntax errors or applying migrations out of order.
Prevention: Test locally first (--local), review generated SQL, regenerate if needed
Error: Type instantiation is excessively deep and possibly infinite
Why: Complex circular references in relations.
Prevention: Use explicit types with InferSelectModel<typeof users>
Error: Stale or incorrect query results
Why: D1 doesn't cache prepared statements like traditional SQLite.
Prevention: Always use .all() or .get() methods, don't reuse statements across requests
Error: Transaction doesn't roll back on error Why: D1 batch API doesn't support traditional rollback. Prevention: Implement error handling with manual cleanup in try/catch
Error: Type errors with strict: true
Why: Drizzle types can be loose.
Prevention: Use explicit return types: Promise<User | undefined>
Error: Cannot find drizzle.config.ts
Why: Wrong file location or name.
Prevention: File must be drizzle.config.ts in project root
Error: Changes not appearing in dev or production
Why: Applying migrations to wrong database.
Prevention: Use --local for dev, --remote for production
Error: Configuration not recognized
Why: Mixing TOML and JSON formats.
Prevention: Use wrangler.jsonc consistently (supports comments)
// ❌ DON'T: Use traditional transactions (fails with D1_ERROR)
await db.transaction(async (tx) => { /* ... */ });
// ✅ DO: Use D1 batch API
const results = await db.batch([
db.insert(users).values({ email: 'test@example.com', name: 'Test' }),
db.insert(posts).values({ title: 'Post', content: 'Content', authorId: 1 }),
]);
// With error handling
try {
await db.batch([...]);
} catch (error) {
console.error('Batch failed:', error);
// Manual cleanup if needed
}
check-versions.sh - Verify package versions are up to date
./scripts/check-versions.sh
Output:
Checking Drizzle ORM versions...
✓ drizzle-orm: 0.44.7 (latest)
✓ drizzle-kit: 0.31.5 (latest)
Claude should load these when you need specific deep-dive information:
When to load:
Required:
drizzle-orm@0.45.1 - ORM runtimedrizzle-kit@0.31.8 - CLI tool for migrationsOptional:
better-sqlite3@12.4.6 - For local SQLite development@cloudflare/workers-types@4.20251125.0 - TypeScript typesSkills:
/drizzle-team/drizzle-orm-docs{
"dependencies": {
"drizzle-orm": "^0.45.1"
},
"devDependencies": {
"drizzle-kit": "^0.31.8",
"@cloudflare/workers-types": "^4.20260103.0",
"better-sqlite3": "^12.5.0"
}
}
This skill is based on production patterns from:
Token Savings: ~60% compared to manual setup Error Prevention: 100% (all 12 known issues documented and prevented) Ready for production! ✅
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.