---
title: "Types (TypeDecl)"
description: "Generated from the doc schema (CatalogDocSchema, @canonify/kernel-catalog/doc): each field’s type, default and description are the schema’s own, and each example is a bundle file that readBundle…"
order: 15
id: "model/type"
---

# Types (TypeDecl)

Generated from the doc schema (`CatalogDocSchema`, @canonify/kernel-catalog/doc): each field’s type, default and description are the schema’s own, and each example is a bundle file that readBundle decodes. A field’s Example is its value in one of those examples.

## TypeDecl {#typedecl}

A type of record.

The `types` slot of the doc holds these, keyed by id. In a bundle each is one file, `types/<name>.json`, named by its `name`, which the file then leaves out.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `id` | [TypeId](shapes.md#typeid) | yes (propose assigns it; a new declaration leaves it out) | — | `"ty_7k2m9q4z1a"` | A type id (`ty_…`); renames keep it. |
| `name` | [Ident](shapes.md#ident) | yes | — | `"customer"` | The type name. |
| `label` | [Text](shapes.md#text) | yes (a draft may leave it out: propose writes a default made from the name, `catalog.label_defaulted`) | — | `"Customer"` | The words shown for it. |
| `idPrefix` | string (matching `^[a-z]{2,5}$`) | no | — | `"bk"` | The prefix of its record ids: 2–5 lower-case letters. |
| `class` | `"data"` \| `"access"` \| `"credential"` \| `"evidence"` \| `"queue"` | no | `"data"` | `"data"` | What the rows are: data. The other classes are the platform’s own, and a doc declaring one is refused (C34). |
| `verbs` | [TypeDecl.verbs](type.md#typedecl.verbs) | no | `{}` | (see the example) | Which generated verbs exist (runtime §4.4); default all. |
| `properties` | [PropertyDecl](property.md#propertydecl)[] | yes | — | (see the example) | The properties, in their default field order. |
| `keys` | [TypeDecl.keys](type.md#typedecl.keys) | no | — | (see the example) | The keys of the type. |
| `temporal` | [TypeDecl.temporal](type.md#typedecl.temporal) | no | — | (see the example) | Rows are effective-dated; compiles to a no_overlap invariant. |
| `invariants` | [InvariantDecl](type.md#invariantdecl)[] | no | — | (see the example) | The rules every write must keep. |
| `owner` | [PropId](shapes.md#propid) | no | — | `"pr_2d4f6h8k0m"` | The required cascade ref to the row this row belongs to. |
| `title` | [TitleDecl](type.md#titledecl) | no | — | `"name"` | A row’s title: pickers, ref display and the title search source. |
| `measures` | Record<[Ident](shapes.md#ident), [MeasureDecl](type.md#measuredecl)> | no | — | `{"count":{"label":"Bookings","expr":"count()"}}` | The declared aggregates, by name (C79). |
| `presentation` | [TypePresentation](view.md#typepresentation) | no | — | (see the example) | How the type is shown (pages). |

`types/customer.json`

```json
{
  "label": "Customer",
  "properties": [
    {
      "kind": "stored",
      "name": "name",
      "label": "Name",
      "type": { "t": "text" },
      "required": true
    }
  ],
  "title": "name"
}
```

`types/booking.json`

```json
{
  "label": "Booking",
  "idPrefix": "bk",
  "class": "data",
  "verbs": { "delete": false, "end": true },
  "properties": [
    {
      "kind": "stored",
      "id": "pr_1c3e5g7j9m",
      "name": "room",
      "label": "Room",
      "type": { "t": "text" },
      "required": true
    },
    {
      "kind": "stored",
      "id": "pr_3a5c7e9g1j",
      "name": "starts",
      "label": "Starts",
      "type": { "t": "date" },
      "required": true
    },
    {
      "kind": "stored",
      "id": "pr_9t1v3x5z7b",
      "name": "ends",
      "label": "Ends",
      "type": { "t": "date" },
      "required": true
    },
    {
      "kind": "ref",
      "id": "pr_2d4f6h8k0m",
      "name": "customer",
      "label": "Customer",
      "target": "ty_7k2m9q4z1a",
      "required": true,
      "onDelete": "cascade"
    }
  ],
  "keys": {
    "natural": ["pr_1c3e5g7j9m"],
    "unique": [["pr_1c3e5g7j9m", "pr_3a5c7e9g1j"]]
  },
  "temporal": {
    "from": "pr_3a5c7e9g1j",
    "to": "pr_9t1v3x5z7b",
    "key": ["pr_1c3e5g7j9m"]
  },
  "invariants": [
    {
      "kind": "check",
      "holds": "ends >= starts",
      "message": { "text": "A booking cannot end before it starts." }
    }
  ],
  "owner": "pr_2d4f6h8k0m",
  "title": {
    "text": "Room {room} from {starts}",
    "params": { "room": "room", "starts": "starts" }
  },
  "measures": { "count": { "label": "Bookings", "expr": "count()" } }
}
```

`types/audit_note.json`

```json
{
  "label": "Audit note",
  "verbs": {
    "create": true,
    "update": false,
    "delete": false,
    "set": false,
    "end": false
  },
  "properties": [
    {
      "kind": "stored",
      "name": "note",
      "label": "Note",
      "type": { "t": "text", "format": "long" },
      "required": true
    }
  ]
}
```

## TypeDecl.verbs {#typedecl.verbs}

Which generated verbs exist (runtime §4.4); default all.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `create` | boolean | no | `true` | `true` | Whether the generated create verb exists. |
| `update` | boolean | no | `true` | `true` | Whether the generated update verb exists. |
| `delete` | boolean | no | `true` | `true` | Whether the generated delete verb exists. |
| `set` | boolean | no | `true` | `true` | Whether the generated set verb exists. |
| `end` | boolean | no | `true` | `true` | Whether the generated end verb exists. |

## TypeDecl.keys {#typedecl.keys}

The keys of the type.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `natural` | [PropId](shapes.md#propid)[] | no | — | `["pr_1c3e5g7j9m"]` | The natural key: required properties that name a row. |
| `unique` | [PropId](shapes.md#propid)[][] | no | — | `[["pr_1c3e5g7j9m","pr_3a5c7e9g1j"]]` | Each list of properties is unique together. |

## TypeDecl.temporal {#typedecl.temporal}

Rows are effective-dated; compiles to a no_overlap invariant.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `from` | [PropId](shapes.md#propid) | yes | — | `"pr_3a5c7e9g1j"` | The start of each row’s period. |
| `to` | [PropId](shapes.md#propid) | yes | — | `"pr_9t1v3x5z7b"` | The end of each row’s period. |
| `key` | [PropId](shapes.md#propid)[] | yes | — | `["pr_1c3e5g7j9m"]` | Required properties whose rows must not overlap. |

## InvariantDecl {#invariantdecl}

A rule every write must keep.

One of, told apart by `kind`:

- [InvariantDecl.check](type.md#invariantdecl.check)
- [InvariantDecl.unique](type.md#invariantdecl.unique)
- [InvariantDecl.no_overlap](type.md#invariantdecl.no_overlap)
- [InvariantDecl.bound](type.md#invariantdecl.bound)

## InvariantDecl.check {#invariantdecl.check}

A row condition.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `kind` | `"check"` | yes | — | `"check"` | A condition on one row. |
| `id` | [InvariantId](shapes.md#invariantid) | yes (propose assigns it; a new declaration leaves it out) | — | — | An invariant id (`iv_…`); renames keep it. |
| `holds` | [Expr](shapes.md#expr) | yes | — | `"ends >= starts"` | The CX condition every row must hold. |
| `message` | [MsgRef](shapes.md#msgref) | yes | — | `{"text":"A booking cannot end before it starts."}` | What a refused write says. |

An item of `invariants` in a type file.

```json
{
  "kind": "check",
  "holds": "total >= 0",
  "message": { "text": "The total cannot be negative." }
}
```

## InvariantDecl.unique {#invariantdecl.unique}

A uniqueness rule.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `kind` | `"unique"` | yes | — | `"unique"` | No two rows share these values. |
| `id` | [InvariantId](shapes.md#invariantid) | yes (propose assigns it; a new declaration leaves it out) | — | — | An invariant id (`iv_…`); renames keep it. |
| `props` | [PropId](shapes.md#propid)[] | yes | — | `["pr_5w7y9a1c3e"]` | The properties whose values are unique together. |
| `where` | [Expr](shapes.md#expr) | no | — | `"active"` | Only rows where this holds count. |

An item of `invariants` in a type file.

```json
{ "kind": "unique", "props": ["pr_5w7y9a1c3e"], "where": "active" }
```

## InvariantDecl.no_overlap {#invariantdecl.no_overlap}

A no-overlap rule.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `kind` | `"no_overlap"` | yes | — | `"no_overlap"` | No two rows with the same key overlap in time. |
| `id` | [InvariantId](shapes.md#invariantid) | yes (propose assigns it; a new declaration leaves it out) | — | — | An invariant id (`iv_…`); renames keep it. |
| `from` | [PropId](shapes.md#propid) | yes | — | `"pr_3a5c7e9g1j"` | The start of each row’s period. |
| `to` | [PropId](shapes.md#propid) | yes | — | `"pr_9t1v3x5z7b"` | The end of each row’s period. |
| `key` | [PropId](shapes.md#propid)[] | yes | — | `["pr_1c3e5g7j9m"]` | Required properties that group rows that must not overlap. |

An item of `invariants` in a type file.

```json
{
  "kind": "no_overlap",
  "from": "pr_3a5c7e9g1j",
  "to": "pr_9t1v3x5z7b",
  "key": ["pr_1c3e5g7j9m"]
}
```

## InvariantDecl.bound {#invariantdecl.bound}

A bound over the type’s rows.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `kind` | `"bound"` | yes | — | `"bound"` | A condition over the rows of the type together. |
| `id` | [InvariantId](shapes.md#invariantid) | yes (propose assigns it; a new declaration leaves it out) | — | — | An invariant id (`iv_…`); renames keep it. |
| `holds` | [Expr](shapes.md#expr) | yes | — | `"count() <= 1000"` | The CX condition every row must hold. |
| `message` | [MsgRef](shapes.md#msgref) | yes | — | `{"text":"At most {max} orders.","params":{"max":"1000"}}` | What a refused write says. |

An item of `invariants` in a type file.

```json
{
  "kind": "bound",
  "holds": "count() <= 1000",
  "message": { "text": "At most {max} orders.", "params": { "max": "1000" } }
}
```

## MeasureDecl {#measuredecl}

A declared aggregate, read by data.aggregate, Aggregate/Stat, nav counts and agents.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `id` | [MeasureId](shapes.md#measureid) | yes (propose assigns it; a new declaration leaves it out) | — | — | A measure id (`ms_…`); renames keep it. |
| `label` | [Text](shapes.md#text) | yes (a draft may leave it out: propose writes a default made from the name, `catalog.label_defaulted`) | — | `"Bookings"` | The words shown for it. |
| `expr` | [Expr](shapes.md#expr) | yes | — | `"count()"` | One CX aggregate over the type’s rows (use site measure, cost at most agg). |
| `groupBy` | [PropId](shapes.md#propid)[] | no | — | `["pr_5n1v3x7c9d"]` | The properties a reader may group it by. |
| `where` | [Expr](shapes.md#expr) | no | — | `"status == 'placed'"` | Only rows where this holds are aggregated. |

An entry of `measures` in a type file, under its name (here `revenue`).

```json
{
  "label": "Revenue",
  "expr": "sum(total)",
  "groupBy": ["pr_5n1v3x7c9d"],
  "where": "status == 'placed'"
}
```

## TitleDecl {#titledecl}

A CX text expression, or a template with CX params that present writes in the reader’s convention.

Type: [Expr](shapes.md#expr) | [MsgRef](shapes.md#msgref).

## SearchSpec {#searchspec}

Makes the property a search source.

| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
| `weight` | `"title"` \| `"body"` | yes | — | `"body"` | The weight class of its search column: title ranks above body. |

The `searchable` of a property.

```json
{ "weight": "title" }
```
