Skip to content
Back to skills

2558 Column Patterns 2d89847b

ASecurity

Complete reference for every Drizzle ORM column type used with Cloudflare D1. All patterns verified against real D1 projects.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
databasestypescriptgosqlgitdatabase

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2558-column-patterns_2d89847b --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2558 Column Patterns 2d89847b?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2558 Column Patterns 2d89847b
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2558-column-patterns-2d89847b/badge)](https://www.skillsdirectory.com/skills/tools-only-2558-column-patterns-2d89847b)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Column Patterns

Complete reference for every Drizzle ORM column type used with Cloudflare D1. All patterns verified against real D1 projects.

## Imports

```typescript
import { sqliteTable, text, integer, real, blob, index, uniqueIndex } from 'drizzle-orm/sqlite-core'
import { relations, sql } from 'drizzle-orm'
```

## Primary Keys

### Text UUID (preferred)

```typescript
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
```

Generates UUIDs at insert time. Works in Workers runtime (crypto.randomUUID is available).

### Integer Autoincrement

```typescript
id: integer('id').primaryKey({ autoIncrement: true }),
```

Use when you need sequential IDs or when the table is insert-heavy and UUID overhead matters.

## Text

### Plain text

```typescript
name: text('name').notNull(),
description: text('description'),  // nullable
```

### Text with enum

```typescript
role: text('role', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('viewer'),
status: text('status', { enum: ['draft', 'published', 'archived'] }).notNull().default('draft'),
```

Stored as TEXT in D1. Drizzle validates at the TypeScript level — no database-level constraint.

## Boolean

D1 has no native BOOLEAN. Use INTEGER with `mode: 'boolean'`:

```typescript
emailVerified: integer('email_verified', { mode: 'boolean' }).notNull().default(false),
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
```

Stored as 0/1 in D1. Drizzle auto-converts to/from `boolean` in TypeScript.

## Timestamps

D1 has no native DATETIME. Use INTEGER with `mode: 'timestamp'`:

```typescript
// Stores as unix epoch seconds, returns as Date object
createdAt: integer('created_at', { mode: 'timestamp' })
  .notNull()
  .$defaultFn(() => new Date()),

updatedAt: integer('updated_at', { mode: 'timestamp' })
  .notNull()
  .$defaultFn(() => new Date()),
```

### Manual unix timestamps (when you don't want Date objects)

```typescript
timestamp: integer('timestamp').notNull().$defaultFn(() => Math.floor(Date.now() / 1000)),
```

## Numbers

### Integer

```typescript
count: integer('count').notNull().default(0),
sortOrder: integer('sort_order'),
```

### Real (float/decimal)

```typescript
price: real('price').notNull(),
latitude: real('latitude'),
longitude: real('longitude'),
```

## JSON

Store as TEXT with `mode: 'json'`. Drizzle handles JSON.stringify/parse automatically.

### Typed JSON (recommended)

```typescript
preferences: text('preferences', { mode: 'json' })
  .$type<{ theme: string; notifications: boolean }>()
  .$defaultFn(() => ({ theme: 'default', notifications: true })),

metadata: text('metadata', { mode: 'json' })
  .$type<Record<string, unknown>>(),

changes: text('changes', { mode: 'json' })
  .$type<Record<string, { old: unknown; new: unknown }>>(),
```

### Untyped JSON (when schema varies)

```typescript
rawData: text('raw_data'),  // manual JSON.stringify/parse
```

Use `{ mode: 'json' }` unless you need to query JSON fields in raw SQL — in that case, use plain `text()` and handle serialisation yourself.

## Foreign Keys

Foreign keys are **always enforced in D1** (cannot disable with PRAGMA).

```typescript
// Inline reference
userId: text('user_id')
  .notNull()
  .references(() => users.id, { onDelete: 'cascade' }),

// With set null
categoryId: text('category_id')
  .references(() => categories.id, { onDelete: 'set null' }),

// Self-referencing
parentId: text('parent_id')
  .references((): AnySQLiteColumn => categories.id),
```

**Cascade options**: `cascade`, `set null`, `restrict`, `no action` (default).

**Migration ordering**: When creating tables with circular FKs, use `PRAGMA defer_foreign_keys = on` at the start of the migration.

## Indexes

Defined in the table function callback (second argument to `sqliteTable`):

```typescript
export const posts = sqliteTable('posts', {
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
  title: text('title').notNull(),
  authorId: text('author_id').notNull().references(() => users.id),
  status: text('status', { enum: ['draft', 'published'] }).notNull(),
  publishedAt: integer('published_at', { mode: 'timestamp' }),
}, (table) => ({
  // Single column index
  authorIdx: index('posts_author_idx').on(table.authorId),

  // Unique index
  slugIdx: uniqueIndex('posts_slug_idx').on(table.slug),

  // Composite index
  statusDateIdx: index('posts_status_date_idx').on(table.status, table.publishedAt),
}))
```

**Naming convention**: `{table}_{column(s)}_{idx|uniq}`.

## Relations

Drizzle relations are query builder helpers — not database-level constraints. Define alongside FKs.

### One-to-many

```typescript
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}))

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}))
```

### Many-to-many (via junction table)

```typescript
export const postTags = sqliteTable('post_tags', {
  postId: text('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
  tagId: text('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
}, (table) => ({
  pk: uniqueIndex('post_tags_pk').on(table.postId, table.tagId),
}))
```

## Type Exports

Always export inferred types for every table:

```typescript
export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert
export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.