# Keys

<Callout type="info" title="Where this fits">
Start here. [Columns](/docs/define/table/columns) are the other fields.
</Callout>

One table has one primary key. A unique is a separate constraint.

### One column

`.primaryKey()` marks that column. Update omits it. Insert still accepts the value, unless the column is generated or guarded.

```ts title="notes.ts"
import { table, t } from "okmodel/pg";

export const notes = table(
  "notes",
  {
    id: t.uuid().primaryKey(), // [!code focus]
    title: t.text(),
  },
);
```

<Callout type="warning" title="Not a composite">
A second `.primaryKey()` is not a composite key. Use [several columns](#several-columns).
</Callout>

### Several columns

`primaryKey` lists the columns, in column order.

```ts title="memberships.ts"
import { table, t } from "okmodel/pg";

export const memberships = table(
  "memberships",
  {
    organizationId: t.uuid(),
    userId: t.uuid(),
  },
  { primaryKey: ["organizationId", "userId"] }, // [!code focus]
);
```

### Unique

A unique is its own constraint. The primary key stays separate.

| Form | Result |
| --- | --- |
| `.unique()` | One column. |
| `unique: { name: [fields] }` | Those fields together, in that order. `name` is the key you choose. |
| `.unique({ global: "reason" })` | On a tenant table, the tenant key is left out. |

#### column

`.unique()` is the constraint for one field.

```ts title="users.ts"
import { table, t } from "okmodel/pg";

export const users = table("users", {
  id: t.identity(),
  email: t.text().unique(), // [!code focus]
});
```

#### several

The table option names the constraint and lists the fields, in column order.

```ts title="logins.ts"
import { table, t } from "okmodel/pg";

export const logins = table(
  "logins",
  {
    id: t.identity(),
    email: t.text(),
    provider: t.text(),
  },
  { unique: { login: ["email", "provider"] } }, // [!code focus]
);
```

#### global

On a tenant table a unique includes the tenant key. `{ global: "reason" }` leaves that key out. `{ global: true, reason }` is the same opt-out.

```ts title="global.ts"
import { schema, table, t } from "okmodel/pg";
import { columnTenancy } from "okmodel/tenancy";

const users = table("users", {
  id: t.identity(),
  email: t.text().unique({ global: "one login email" }), // [!code focus]
});

export const app = schema({
  tables: [users],
  tenancy: columnTenancy({ key: "tenantId", type: "uuid" }),
});
```

<Callout type="error" title="OKM1060">
An empty reason is `unique() global needs a reason.` `{ global: true }` with no reason is `Unique users.email sets global and needs a reason.`
</Callout>

### id()

Needs PostgreSQL 18 or higher.

`t.id()` is a uuid primary key. The schema [id default](/docs/define/schema/defaults) is what a bare call means. A column option wins. [OKID](/docs/define/table/columns#okid) is `t.id({ default: okid() })`, stored as `text`.

| Call | What fills it | Insert |
| --- | --- | --- |
| `t.id()` | Database `gen_random_uuid()`, unless the schema default says otherwise. | Omitted. |
| `t.id({ default: "uuidv7" })` | Database `uuidv7()`. Needs Postgres 18. | Omitted. |
| `t.id({ default: "uuidv4" })` | Database `gen_random_uuid()`, the same as a bare call. | Omitted. |
| `t.id({ default: uuidv7 })` | The client generator. Nothing is stored in the catalog. | Omitted. |
| `t.id({ default: "none" })` | Nothing. | Required. Still omitted from update. |
| `t.identity()` | `generated always as identity`. A bigint. | Omitted. |

#### database

A bare `t.id()` stores `gen_random_uuid()`. Random ids are fine for most tables. A large write-heavy table can use `t.id({ default: "uuidv7" })`: the ids are time-ordered and the index stays local. That function needs Postgres 18 and `minPostgres: 18`.

```ts title="id.ts"
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.id(), // [!code focus]
  title: t.text(),
});
```

#### uuidv4

`"uuidv4"` stores `gen_random_uuid()`.

```ts title="uuidv4.ts"
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.id({ default: "uuidv4" }), // [!code focus]
  title: t.text(),
});
```

#### generator

A function from `okmodel/ids` runs on insert. The catalog stores no default.

```ts title="generator.ts"
import { uuidv7 } from "okmodel/ids";
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.id({ default: uuidv7 }), // [!code focus]
  title: t.text(),
});
```

#### none

`"none"` takes the id on insert. Update still omits it.

```ts title="supplied.ts"
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.id({ default: "none" }), // [!code focus]
  title: t.text(),
});
```

#### identity

`t.identity()` is a generated bigint, not a uuid.

```ts title="identity.ts"
import { table, t } from "okmodel/pg";

export const notes = table("notes", {
  id: t.identity(), // [!code focus]
  title: t.text(),
});
```

<Callout type="error" title="OKM1060">
A default other than `uuidv7`, `uuidv4`, `none`, or a client generator is refused.
</Callout>
