d1-drizzle-schema — Claude Code skill: what it does, how to install
Generates Drizzle ORM schemas tailored for Cloudflare D1 quirks. Helps scaffold tables, write migration scripts, and document database structures.
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a claude-codeWhat d1-drizzle-schema does
Standard SQLite patterns cause subtle bugs when applied directly to Cloudflare D1. D1 enforces foreign keys at all times and does not offer native boolean or datetime types. This skill configures columns using integer({ mode: 'boolean' }) and integer({ mode: 'timestamp' }), stores JSON fields as text, and maps relations.
The generator outputs TypeScript schemas, inferred $inferSelect and $inferInsert types, a starter drizzle.config.ts, and DATABASE_SCHEMA.md. It also writes migration scripts for wrangler d1 migrations apply targeting both local and remote environments.
Cloudflare caps bound parameters at 100 per statement. A regular bulk insert crashes immediately when row count multiplied by column count exceeds that limit. The skill writes chunked insert loops based on Math.floor(100 / COLUMNS_PER_ROW) to keep payloads safe.
What it helps with
- generate Drizzle ORM schemas aligned with Cloudflare D1 constraints
- store boolean flags and timestamps as integers correctly
- batch bulk inserts around the 100 bound parameter ceiling
- add local and remote wrangler migration commands to package.json
- document tables and relationships in DATABASE_SCHEMA.md
Example prompts for the agent
- Generate a Drizzle schema for users and organisations in Cloudflare D1
- Set up D1 migration scripts and drizzle.config.ts for this project
- Scaffold a batch insert helper for D1 respecting parameter limits
How to install d1-drizzle-schema
1Skills CLI (Vercel)
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a claude-code2Manually: put the files in place
~/.claude/skills/d1-drizzle-schema/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/d1-drizzle-schema/
1Skills CLI (Vercel)
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a codex2Manually: put the files in place
.agents/skills/d1-drizzle-schema/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a cursor2Manually: put the files in place
.cursor/skills/d1-drizzle-schema/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install jezweb/claude-skills d1-drizzle-schema2Manually: put the files in place
.github/skills/d1-drizzle-schema/SKILL.md1Skills CLI (Vercel)
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a gemini-cli2Platform CLI
gemini skills install https://github.com/jezweb/claude-skills --path archive/plugins/cloudflare/skills/d1-drizzle-schemaManually: put the files in place
.devin/skills/d1-drizzle-schema/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about d1-drizzle-schema
How to install d1-drizzle-schema in Claude Code?
Run npx skills add jezweb/claude-skills --skill d1-drizzle-schema -a claude-code in your terminal. The skill file is placed in ~/.claude/skills/d1-drizzle-schema/SKILL.md.
What does d1-drizzle-schema do?
It scaffolds Drizzle ORM schemas specifically configured for Cloudflare D1. The skill covers column modes for timestamps and booleans, query relations, type exports, and documentation.
How does the skill handle bulk inserts in Cloudflare D1?
D1 caps bound query parameters at 100. The skill calculates batch sizes using Math.floor(100 / COLUMNS_PER_ROW) and chunks insert operations across separate queries.
Source text
SKILL.md · MIT
---
name: d1-drizzle-schema
description: "Generate Drizzle ORM schemas for Cloudflare D1 databases with correct D1-specific patterns. Produces schema files, migration commands, type exports, and DATABASE_SCHEMA.md documentation. Handles D1 quirks: foreign keys always enforced, no native BOOLEAN/DATETIME types, 100 bound parameter limit, JSON stored as TEXT. Use when creating a new database, adding tables, or scaffolding a D1 data layer."
compatibility: claude-code-only
---
# 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
| Feature | Standard SQLite | D1 |
|---------|-----------------|-----|
| Foreign keys | OFF by default | **Always ON** (cannot disable) |
| Boolean type | No | No — use `integer({ mode: 'boolean' })` |
| Datetime type | No | No — use `integer({ mode: 'timestamp' })` |
| Max bound params | ~999 | **100** (affects bulk inserts) |
| JSON support | Extension | **Always available** (json_extract, ->, ->>) |
| Concurrency | Multi-writer | **Single-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:
```typescript
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](references/column-patterns.md) for the full type reference.
### Step 3: Add Relations
Drizzle relations are query builder helpers (separate from FK constraints):
```typescript
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
```typescript
export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert
```
### Step 5: Set Up Drizzle Config
Copy [assets/drizzle-config-template.ts](assets/drizzle-config-template.ts) to `drizzle.config.ts` and update the schema path.
### Step 6: Add Migration Scripts
Add to `package.json`:
```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:
```typescript
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
```typescript
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
| When | Read |
|------|------|
| D1 vs SQLite, JSON queries, limits | [references/d1-specifics.md](references/d1-specifics.md) |
| Column type patterns for Drizzle + D1 | [references/column-patterns.md](references/column-patterns.md) |
## Assets
| File | Purpose |
|------|---------|
| [assets/drizzle-config-template.ts](assets/drizzle-config-template.ts) | Starter drizzle.config.ts for D1 |
| [assets/schema-template.ts](assets/schema-template.ts) | Example schema with all common D1 patterns |
© jezweb · MIT · jezweb/claude-skills