# Type rules (/catalog/type-rules)

The rules a type you register must meet: its identifier, label and plural, parent, compatible types, display hints, roles, link field, and merge, version and retention policies.



**Marfa refuses a definition that breaks a rule with `400 invalid_schema`** unless the rule names another answer. [Define a type](/build/define-a-type) shows how to register one, and [Field kinds](/catalog/field-kinds) has the rules for its fields.

## Identifier [#identifier]

[Items and types](/items-and-types) explains the namespaces a type can sit in.

* **Grammar:** two or more dot-separated segments, each starting with a lowercase letter and holding only lowercase letters, digits, hyphens and underscores. At most 128 characters, and exactly two segments after `app`, as in `app.readinglist.entry`. Anything else is refused `400 validation_error`.
* **`core`, `system` and `marfa`:** refused `403 forbidden`, whatever the credential.
* **Other reserved roots:** every root in the list below except `app`, `user`, `core`, `system` and `marfa` is refused `400 validation_error`. A root is the whole first segment, so `core-pub` is allowed.

### Reserved type roots [#reserved-type-roots]

`app`, `audit`, `blobs`, `config`, `connectors`, `content`, `core`, `edge`, `grants`, `instance`, `items`, `keys`, `marfa`, `metadata`, `profile`, `schema`, `space`, `system`, `user`, `webhooks`

## Label and plural [#label-and-plural]

A type's `label` names one item, such as `Recipe`, and its `plural` a list of them, such as `Recipes`. Each is optional, at most 100 characters.

* **A missing label is made** from the identifier's last segment, split into capitalized words at each hyphen and underscore: `user.reading-list` is `Reading List`.
* **A missing plural is made** from the label when the type is read, by the regular English rule: `Story` makes `Stories`. Name it where the rule is wrong, as in `People`. `GET /types` answers only a declared plural.

## Fields [#fields]

A type's `fields` follow [Field kinds](/catalog/field-kinds). A field can't take a name that every item already has: Marfa refuses it with `400 property_shadows_field`.

### Reserved field names [#reserved-field-names]

`capture_latitude`, `capture_longitude`, `created_at`, `id`, `occurred_at`, `properties`, `schema_version`, `source`, `source_id`, `state`, `tier`, `trashed_at`, `trashed_by_cascade`, `trashed_with`, `type`, `updated_at`, `version`

## Parent and inheritance [#parent-and-inheritance]

| Rule | Marfa allows or refuses |
| - | - |
| `parent` | Names a registered type, with no loop and at most ten ancestors. Refused `400 validation_error`. |
| A subtype may | Add fields. Redeclare an inherited field, or give it a new `description` or `label`. Make an inherited field required. Add a range to an inherited `number` or `integer` field, or narrow its range. Add status words. |
| A subtype may not | Change an inherited field's `type`, `format`, `items_type`, `items_fields`, `searchable`, `maxLength`, `maxItems` or `enum_values`. Widen or drop its range. Make a required field optional. Refused `400 inheritance_violation`, naming the field. |
| Write access | A new `parent` that isn't a platform type needs write on that type; keeping one doesn't. Refused `403 type_not_permitted`. |

**A parent change that breaks a subtype is refused the same way.** A new build can still give a shipped type a field that a registered subtype declares in another shape: `GET /types` lists the field in the subtype's `conflicts`, as [Upgrade](/run/upgrade#types-a-new-build-leaves-in-conflict) shows.

### Status words [#status-words]

A subtype inherits its parent's [status field](/catalog/field-kinds#status-words) and its words. To add words, it declares the same field with only the new ones, such as `blocked` in the `in_progress` group. A word a type above already declares, the field under another name, and a parent change that leaves a subtype's field with no words are refused `400 inheritance_violation`.

### Compatible with [#compatible-with]

`compatible_with` names a type that yours is readable as, without inheriting from it. A claim that doesn't hold, or that names an unregistered type, is refused `422 compatible_with_violation`. The claim holds when:

* Yours requires every field the target requires.
* Each target field that yours declares or inherits has the same kind. A `string` target field also accepts `url`, `email`, `datetime`, `date`, `thumbnail`, `enum` or `status`, and a `number` accepts `integer`.
* An `array` has the same `items_type` and `items_fields`. An `enum` takes values from the target's, and a `status` field takes words from the target's, each in the same group.
* A range is no wider than the target's, and a `format` the target names is the same on yours (a `currency` field also reads as a plain `string`).

A replacement or deletion that breaks another type's claim is refused the same way, naming the claimants in `details.dependent_type_ids`. Naming a new target that isn't a platform type needs write on it, as a new `parent` does; keeping one doesn't. Refused `403 type_not_permitted`.

## Display hints [#display-hints]

`display_hints` names the field that names an item, the field that holds its Markdown text, whether that text can name it, and an icon. [Display hints](/catalog/display-hints) has their rules, how Marfa names an item from them and the icon the owner chooses.

## Roles [#roles]

`roles` takes `container` alone. A `container` type can be the target of an [`in-collection`](/catalog/edges#in-collection) edge.

A type also plays its ancestors' roles. [List types](/api-reference/types/listTypes) answers `roles`, every role it plays, and `own_roles`, those it declares. **When you [replace a type](/api-reference/types/replaceType), send its `own_roles` as `roles`.**

## Link field [#link-field]

`link_field` names a `string` field, declared or inherited, that holds each item's own ID at the vendor that writes the type. A [connector](/build/connector-push#send-a-new-item-to-the-vendor) finds an item by it.

* **The value is unique within the type**, in every state. A write that gives it to a second item is refused `409 link_taken`, and `details.existing_id` names the holder. An empty value is no link.
* **Marfa refuses** a field that isn't a declared or inherited `string`, or whose name holds `"` or `\`: `400 invalid_schema`.
* **A subtype inherits the field, not the link**, and may name its own.

## Merge policy [#merge-policy]

`fields` maps a field the type declares or inherits to a strategy, and `default` covers the rest. Left out, `default` is `last_writer_wins`.

| Strategy | When a later write collides with another on the field |
| - | - |
| `last_writer_wins` | The item takes the later writer's value. |
| `keep_both_copies` | The item keeps its value, and the later writer's value goes to a new item tagged `conflicted-copy`. |

## Version policy [#version-policy]

`version_policy` sets how long Marfa keeps an item's earlier versions. The `version-thinning` [housekeeping job](/run/housekeeping) removes the older ones it doesn't keep, and leaves an item with two versions or fewer alone.

| Key | Marfa keeps |
| - | - |
| `recent_days` | Every version, back this many days. |
| `daily_snapshot_days` | One a day, back this many days. |
| `weekly_snapshot_days` | One a week, back this many days. |
| `max_versions` | At most this many, dropping the oldest first. |

Each is a whole number of at least 1, and no window ends sooner than the one above it. Marfa refuses a policy that breaks either rule with `400 invalid_schema`, naming the key. **A key left out** comes from the parent type, then the instance's defaults.

## Retention policy [#retention-policy]

`retention_policy` takes only `feed_days`:

| `feed_days` | Effect |
| - | - |
| A whole number from 1 to 36500 | The `feed-expiry` [housekeeping job](/run/housekeeping) moves the type's feed items to the bin once more than that many days have passed since their `created_at`. A natural-key upsert keeps `created_at`, so a refresh doesn't restart the count. |
| `null` | The type keeps its feed items, even when a parent sets a window. |
| Left out | The type takes its parent's window, and keeps its feed items where no type above sets one. |

`GET /types/{id}` shows the merge, version and retention policies after inheritance, and `GET /types` only those the type declares.

The rules behind this page are in the [types](https://github.com/withmarfa/marfa/blob/main/conformance/spec/types.md), [items](https://github.com/withmarfa/marfa/blob/main/conformance/spec/items.md), [versions](https://github.com/withmarfa/marfa/blob/main/conformance/spec/versions.md), [edges](https://github.com/withmarfa/marfa/blob/main/conformance/spec/edges.md) and [housekeeping](https://github.com/withmarfa/marfa/blob/main/conformance/spec/housekeeping.md) chapters of the contract.
