# Fields
URL: /docs/schema/fields
LLM index: /llms.txt
Description: Field builders, defaults, mapping, nullability, uniqueness, and references.

# Fields

Field builders define the scalar shape of each model.

## Scalar builders

### `id()`

```ts
id: id();
```

- runtime output type: `string`
- unique by default
- generated as an id by default

For numeric primary keys, pass an explicit type:

```ts
id: id({ type: "integer" });
```

- runtime output type: `number`
- useful when an app or integration needs integer-shaped identifiers
- works well for manually assigned numeric IDs

If you want the runtime or database to generate integer IDs for you, opt into
increment semantics:

```ts
id: id({ type: "integer", generated: "increment" });
```

- runtime output type: `number`
- SQL, Drizzle, Kysely, Prisma, and the memory driver can generate the value
- MongoDB and Mongoose still require manual numeric IDs today

### `string()`

```ts
email: string();
```

- runtime output type: `string`

### `boolean()`

```ts
emailVerified: boolean();
```

- runtime output type: `boolean`

### `datetime()`

```ts
createdAt: datetime();
```

- runtime output type: `Date`

### `integer()`

```ts
loginCount: integer();
```

- runtime output type: `number`

### `enumeration()`

```ts
status: enumeration(["draft", "published"]);
```

- runtime output type: the string union from the values you pass in
- generated as a real enum where the target supports it
- TypeScript reserves the `enum` keyword, so the builder is named `enumeration()`

### `bigint()`

```ts
quota: bigint();
```

- runtime output type: `bigint`
- useful for 64-bit counters, quotas, and ids that should not round through `number`

### `decimal()`

```ts
balance: decimal();
```

- runtime output type: `string`
- keeps precision stable across runtimes instead of routing decimal math through JavaScript `number`
- decoded values are normalized strings, so `"12.50"` may round-trip as `"12.5"`

### `json()`

```ts
metadata: json<{
  plan: string;
  scopes: string[];
}>();
```

- runtime output type: the JSON shape you pass to `json<T>()`
- useful for provider metadata, settings blobs, and structured plugin state

## Field modifiers

### `.unique()`

```ts
email: string().unique();
```

Marks the field as unique in the schema and generated outputs.

Use this for single-field uniqueness. For compound uniqueness such as
`provider + accountId`, use model-level `constraints.unique` on `model(...)`
instead.

### `.nullable()`

```ts
bio: string().nullable();
```

Changes the runtime output type from `string` to `string | null`.

### `.default(...)`

```ts
role: string().default("member");
```

Stores a literal default value.

This works well for string, boolean, integer, bigint, decimal, and enum fields
today. JSON defaults can still be applied by runtime drivers, but generated
schema support is more limited and should be treated as backend-specific for
now.

### `.defaultNow()`

```ts
createdAt: datetime().defaultNow();
```

Marks the field as generated from the current time.

### `.references("model.field")`

```ts
userId: string().references("user.id");
```

Adds foreign-key-style metadata that generators can translate.

### `.map("column_name")`

```ts
email: string().map("email_address");
```

Keeps the logical API field name as `email` while generating the physical column
name `email_address`.

### `.describe("...")`

```ts
email: string().describe("Canonical login email");
```

Adds descriptive metadata to the manifest/docs layer.

## Example model

```ts
user: model({
  table: "users",
  fields: {
    id: id(),
    email: string().unique().map("email_address"),
    name: string(),
    emailVerified: boolean().default(false),
    loginCount: integer().default(0).map("login_count"),
    tier: enumeration(["free", "pro", "enterprise"]).default("free"),
    quota: bigint().default(0n).map("quota_bigint"),
    balance: decimal().default("0.00"),
    createdAt: datetime().defaultNow(),
    bio: string().nullable(),
  },
});
```

Numeric IDs use the same DSL:

```ts
auditEvent: model({
  table: "audit_events",
  fields: {
    id: id({ type: "integer" }),
    email: string().unique(),
  },
});
```

Generated numeric IDs use the same builder:

```ts
auditEvent: model({
  table: "audit_events",
  fields: {
    id: id({ type: "integer", generated: "increment" }),
    email: string().unique(),
  },
});
```

## What generators care about

Field data is especially important because it drives:

- Prisma field types
- Drizzle column definitions
- SQL column definitions
- nullability
- unique constraints
- references
- generated/default behavior

The current generators and live runtimes support:

- `integer()` across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, Drizzle, MongoDB, and Mongoose
- `json()` across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, MongoDB, and Mongoose
- `enumeration()` across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoose
- `bigint()` across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoose
- `decimal()` across Prisma, Drizzle, safe SQL, memory, Prisma runtime, SQL, Kysely, MongoDB, and Mongoose

For SQLite-backed bigint tests, the local matrix enables the underlying
`node:sqlite` big-int read mode so values do not get truncated back into
JavaScript `number`.

## Mapping example

```ts
email: string().unique().map("email_address");
```

Logical side:

- `user.email`

Physical side:

- `users.email_address`

This is a big part of why the schema DSL is useful for reusable packages: the
library can keep a stable logical field name even when the physical database
surface needs a different name.