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 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

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-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

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

RuleMarfa allows or refuses
parentNames a registered type, with no loop and at most ten ancestors. Refused 400 validation_error.
A subtype mayAdd 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 notChange 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 accessA 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 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 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 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, 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

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

StrategyWhen a later write collides with another on the field
last_writer_winsThe item takes the later writer's value.
keep_both_copiesThe 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.

KeyMarfa keeps
recent_daysEvery version, back this many days.
daily_snapshot_daysOne a day, back this many days.
weekly_snapshot_daysOne a week, back this many days.
max_versionsAt 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_daysEffect
A whole number from 1 to 36500The 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.
nullThe type keeps its feed items, even when a parent sets a window.
Left outThe 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.

On this page