🎯

drizzle-orm

🎯Skill

from epicenterhq/epicenter

VibeIndex|
What it does

Enables type-safe and performant column definitions with zero-overhead branded types for Drizzle ORM database interactions.

πŸ“¦

Part of

epicenterhq/epicenter(28 items)

drizzle-orm

Installation

git cloneClone repository
git clone https://github.com/EpicenterHQ/epicenter.git
BunRun with Bun
bun install # Will prompt to upgrade if your Bun version is too old
BunRun with Bun
bun dev
BunRun with Bun
bun install # Reinstall dependencies
BunRun with Bun
bun install
πŸ“– Extracted from docs: epicenterhq/epicenter
6Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Drizzle ORM patterns for type branding and custom types. Use when working with Drizzle column definitions, branded types, or custom type conversions.

Overview

# Drizzle ORM Guidelines

Use $type<T>() for Branded Strings, Not customType

When you need a column with a branded TypeScript type but no actual data transformation, use $type() instead of customType.

The Rule

If toDriver and fromDriver would be identity functions (x) => x, use $type() instead.

Why

Even with identity functions, customType still invokes mapFromDriverValue on every row:

```typescript

// drizzle-orm/src/utils.ts - runs for EVERY column of EVERY row

const rawValue = row[columnIndex]!;

const value = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);

```

Query 1000 rows with 3 date columns = 3000 function calls doing nothing.

Bad Pattern

```typescript

// Runtime overhead for identity functions

customType<{ data: DateTimeString; driverParam: DateTimeString }>({

dataType: () => 'text',

toDriver: (value) => value, // called on every write

fromDriver: (value) => value, // called on every read

});

```

Good Pattern

```typescript

// Zero runtime overhead - pure type assertion

text().$type();

```

$type() is a compile-time-only type override:

```typescript

// drizzle-orm/src/column-builder.ts

$type(): $Type {

return this as $Type;

}

```

When to Use customType

Only when data genuinely transforms between app and database:

```typescript

// JSON: object ↔ string - actual transformation

customType<{ data: UserPrefs; driverParam: string }>({

toDriver: (value) => JSON.stringify(value),

fromDriver: (value) => JSON.parse(value),

});

```

Keep Data in Intermediate Representation

Prefer keeping data serialized (strings) through the system, parsing only at the edges (UI components).

The principle: If data enters serialized and leaves serialized, keep it serialized in the middle. Parse at the edges where you actually need the rich representation.

Example: DateTimeString

Instead of parsing DateTimeString into Temporal.ZonedDateTime at the database layer:

```typescript

// Bad: parse on every read, re-serialize at API boundaries

customType<{ data: Temporal.ZonedDateTime; driverParam: string }>({

fromDriver: (value) => fromDateTimeString(value),

});

```

Keep it as a string until the UI actually needs it:

```typescript

// Good: string stays string, parse only in date-picker component

text().$type();

// In UI component:

const temporal = fromDateTimeString(row.createdAt);

// After edit:

const updated = toDateTimeString(temporal);

```