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 shows how to register one, and Field kinds has the rules for its fields.
Identifier
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 inapp.readinglist.entry. Anything else is refused400 validation_error. core,systemandmarfa: refused403 forbidden, whatever the credential.- Other reserved roots: every root in the list below except
app,user,core,systemandmarfais refused400 validation_error. A root is the whole first segment, socore-pubis allowed.
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
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-listisReading List. - A missing plural is made from the label when the type is read, by the regular English rule:
StorymakesStories. Name it where the rule is wrong, as inPeople.GET /typesanswers only a declared plural.
Fields
A type's fields follow 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
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
| 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 shows.
Status words
A subtype inherits its parent's status field 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 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
stringtarget field also acceptsurl,email,datetime,date,thumbnail,enumorstatus, and anumberacceptsinteger. - An
arrayhas the sameitems_typeanditems_fields. Anenumtakes values from the target's, and astatusfield takes words from the target's, each in the same group. - A range is no wider than the target's, and a
formatthe target names is the same on yours (acurrencyfield also reads as a plainstring).
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 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 has their rules, how Marfa names an item from them and the icon the owner chooses.
Roles
roles takes container alone. A container type can be the target of an in-collection edge.
A type also plays its ancestors' roles. List types answers roles, every role it plays, and own_roles, those it declares. When you replace a type, send its own_roles as roles.
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 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, anddetails.existing_idnames 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
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 sets how long Marfa keeps an item's earlier versions. The version-thinning housekeeping job 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 takes only feed_days:
feed_days | Effect |
|---|---|
| A whole number from 1 to 36500 | The feed-expiry housekeeping job 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, items, versions, edges and housekeeping chapters of the contract.