> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myme.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Types

> The schema system — core types, custom types, inheritance, and the full core catalog

Types are schemas. Every item has a `type`, and its `properties` are validated against that type's schema on write.

## Custom types are the default path

`core.*` is small and narrow on purpose. Most apps register their own types — inheritance from `core.*` is optional, not expected.

A type earns core-set inclusion only by passing four tests:

1. **Life-noun.** Names something a person would describe as a thing in their life, not infrastructure or app state.
2. **Stable shape.** Canonical fields don't change with context.
3. **Cross-app meaning.** Multiple unrelated apps want to read each other's writes in the raw form, without translation.
4. **Stewardship.** The core maintainers are willing and able to own the schema long-term.

Most candidates fail. That's the feature — the core is the lingua franca precisely because it's disciplined. The same rule applies recursively to subtypes (see [Why core file subtypes stop where they do](#why-core-file-subtypes-stop-where-they-do)).

Tool-specific things are not core. Memories, summaries, embeddings, AI-specific artefacts — none have broad shape consensus across producers, so they become custom types: `claude.memory`, `chatgpt.memory`, `cursor.memory`. Different shapes are honest.

## Namespacing

Five tiers. Reserved roots tell a reader (or parser) the shape at first glance.

| Namespace               | Purpose                                                                          |
| ----------------------- | -------------------------------------------------------------------------------- |
| `core.*`                | Platform-shipped, immutable, narrow.                                             |
| `system.*`              | Platform-internal operational types. See [System types](/concepts/system-types). |
| `app.<app-name>.<type>` | App-published types. The `app.` prefix separates app trust from publisher trust. |
| `user.<type>`           | Unpublished personal types, space-scoped.                                        |
| `<publisher>.<type>`    | Community-published, installable.                                                |

Type identifiers use dots. No forward slashes: `demo.web_gallery`, not `demo/web-gallery`.

The reserved roots — `core`, `system`, `app`, `user`, `marfa` — cannot be claimed as publisher handles. Any first segment that isn't a reserved root is interpreted as a publisher handle.

User types are flat by convention but can extend via inheritance where it earns its place. Sibling is the default; inherit only when every ancestor field is semantically required for your type. See [Sibling vs child](#sibling-vs-child) below and the [Namespace concept page](/concepts/namespaces) for the full grammar.

### Why core file subtypes stop where they do

`core.file` has three subtypes: `image`, `audio`, `video`. There is no `core.file.document`.

`core.file.image` carries `width` and `height`; image viewers rely on those. `core.file.audio` and `core.file.video` carry `duration` (plus dimensions for video). `core.file.document` doesn't earn a subtype — PDFs, Word docs, Markdown, plain text, EPUBs don't share fields beyond what `core.file` already carries (`blob_ref`, `mime_type`); a generic file viewer reads them all the same way.

For the full rubric behind the boundary, see [Authoring types — when a subtype earns its place](/concepts/authoring-types#when-a-subtype-earns-its-place).

## The core catalog

22 types across four families. Required fields only — full schemas in the [API reference](/api-reference/types/list-types).

<AccordionGroup>
  <Accordion title="Standalone (6)">
    | Type             | Purpose                          | Required       |
    | ---------------- | -------------------------------- | -------------- |
    | `core.note`      | User-authored text               | `body`         |
    | `core.bookmark`  | Saved external content           | —              |
    | `core.task`      | Something to be done             | `title`        |
    | `core.event`     | Something that happens at a time | `title`        |
    | `core.highlight` | User engagement with content     | `text`         |
    | `core.message`   | Cross-platform message           | `body`, `from` |

    `core.highlight` carries its highlighted parent via a `references` edge, not an embedded reference. `core.message` is a light cross-platform handle — threading reuses the existing `in-thread` and `parent-of` edges; tool-specific richness (reactions, read receipts, channel ids) lives in app namespaces, not core. `core.note` and the optional bodies on `core.bookmark` and `core.task` are markdown.
  </Accordion>

  <Accordion title="Entity (3)">
    | Type                 | Purpose              | Required           |
    | -------------------- | -------------------- | ------------------ |
    | `core.entity`        | Things with identity | `name`             |
    | `core.entity.person` | People               | `name` (inherited) |
    | `core.entity.place`  | Locations            | `name` (inherited) |

    `core.entity.person` adds `given_name`, `family_name`, pronouns, organization, job title, birthday. `core.entity.place` adds address components plus latitude / longitude / altitude / timezone.
  </Accordion>

  <Accordion title="Media (9)">
    | Type                    | Purpose                       | Required            |
    | ----------------------- | ----------------------------- | ------------------- |
    | `core.media`            | Content the user engages with | `title`             |
    | `core.media.book`       | Books                         | `title` (inherited) |
    | `core.media.article`    | Articles                      | `title` (inherited) |
    | `core.media.film`       | Films                         | `title` (inherited) |
    | `core.media.song`       | Songs                         | `title` (inherited) |
    | `core.media.album`      | Albums                        | `title` (inherited) |
    | `core.media.podcast`    | Podcast episodes              | `title` (inherited) |
    | `core.media.series`     | TV series                     | `title` (inherited) |
    | `core.media.tv_episode` | TV episodes                   | `title` (inherited) |

    All subtypes inherit the shared media fields (`author`, `url`, `publisher`, `published_at`, `image_url`, `language`, `body`, `notes`) and add their own (`isbn`, `duration`, `section`, `track_number`, etc.).
  </Accordion>

  <Accordion title="File (4)">
    | Type              | Purpose        | Required                                               |
    | ----------------- | -------------- | ------------------------------------------------------ |
    | `core.file`       | Binary content | `blob_ref`, `mime_type`                                |
    | `core.file.image` | Images         | `blob_ref`, `mime_type`, `width`, `height`             |
    | `core.file.audio` | Audio          | `blob_ref`, `mime_type`, `duration`                    |
    | `core.file.video` | Video          | `blob_ref`, `mime_type`, `width`, `height`, `duration` |

    `blob_ref` is a SHA-256 content hash — see [Items](/concepts/items#attachments) for the blob upload flow.
  </Accordion>
</AccordionGroup>

<Info>
  `core.message` is intentionally light — `body` + `from` + optional `to`. Tool-specific richness (reactions, read receipts, channel ids, attachment vocabularies) varies too much per producer; apps and publishers register their own message types in their own namespaces (e.g. `slack.message`, `apple.imessage`) when they need it.

  There is no `core.collection` — collection-like groupings are custom types plus `parent-of` edges. See [Edges](/concepts/edges#grouping).

  There is no `core.thread` — threads are implicit, defined by `in-thread` edges. See [Edges](/concepts/edges#threads-are-implicit).
</Info>

## Custom types

Register a custom type at runtime:

```json theme={null}
POST /types
{
  "id": "readwise.reader_document",
  "version": 1,
  "parent": "core.bookmark",
  "fields": {
    "location": { "type": "enum", "enum_values": ["inbox", "later", "shortlist", "archive", "feed"] },
    "word_count": { "type": "integer" },
    "reading_time": { "type": "integer" },
    "html_content": { "type": "string" },
    "parsed_at": { "type": "datetime" }
  }
}
```

Tenant-admin for tenant types; platform-only for `core.*` and `system.*`. Core types can't be modified or deleted via the API.

## Sibling vs child

A custom type can be a **sibling** of a core type (its own shape, optionally declaring `compatible-with: core.<type>`) or a **child** that inherits.

Both are first-class. Visual schema builders default to sibling — AI-assisted authoring tends toward over-inheritance, and a bad inheritance choice is hard to recover from (children cannot redefine ancestor fields). Sibling is recoverable; you can always add inheritance later.

The rubric: inherit only if every ancestor field is semantically required for your type. Otherwise sibling, optionally `compatible-with`.

`compatible-with` is a server-checkable declaration: the platform verifies at registration that your type is a structural superset of the named type, optionally under name mappings. It is not a free-text tag — claims that don't hold are rejected.

Inheritance is single-parent and additive-only. Children may add fields; they cannot redefine or reshape ancestor fields.

**Cross-discoverability differs between the two.** A child appears in parent-type queries automatically — `?type=core.bookmark` returns inheriting subtypes. A sibling with `compatible-with` does not; consumers must know the type identifier to query for it. Prefer inheritance when cross-readability by generic clients matters; stay sibling when the shape genuinely diverges. See [Authoring types — cross-discoverability](/concepts/authoring-types#cross-discoverability-and-the-inheritance-choice) for the deeper trade-off.

## Inheritance

A custom type can inherit from a core type.

### Reads inherit down

`GET /items?type=core.bookmark` returns both `core.bookmark` items and `readwise.reader.document` items. Each carries its actual `type`; inclusion is by inheritance. A generic reader querying `core.bookmark` can rely on every result having the `core.bookmark` contract — `title`, `body`, `url` mean the same thing across children.

### Writes require the exact type

A credential with `write core.bookmark` can't update a `readwise.reader.document` — writes require the specific type in scope. This prevents cross-app write-through corruption while preserving the read-side interop benefits.

### The inheritance rule

**Inherited fields keep their parent-type meaning in every descendant.** A child type may not redefine what a parent field means. If a child needs different content, it adds a new property alongside — it doesn't repurpose the parent's.

Example: `core.bookmark.body` is the user's annotation on the bookmark. A Reader-style child type that wants to carry the parsed article text adds `parsed_body` (or similar) — it doesn't redefine `body` to mean the article content.

Attempting to redefine a parent field at registration returns `400 inheritance_violation`.

### Schema evolution across inheritance

Items carry `schema_version`. Incrementing a parent type's schema doesn't automatically re-validate inheriting-type items. Inheriting types declare which parent versions they're compatible with and migrate when ready.

## Registration

* **Runtime registration.** `POST /types` with a JSON schema. Admin only within the space; reserved-root namespaces (`core.*`, `system.*`) reject non-platform credentials. Types become available in the space.
* **Schema evolution.** Types carry a version. Registering an updated type computes a structural diff against the prior version; the version bump must match the diff class (additive → minor; field removed or required-tightened → major; descriptive-only → patch). Mismatched bumps are rejected at registration. See [Authoring types](/concepts/authoring-types).
* **Global publication.** Community types are published under a publisher handle to the Marfa type registry; published types are referenceable by any space that opts in. See [Handles and publishers](/concepts/handles-and-publishers).

## Field types

Type field definitions support:

* `string`, `integer`, `number`, `boolean`, `array`
* `url`, `email`, `date`, `datetime` (string types with semantic validation)
* `enum` (with `enum_values`)
* Nested objects via `fields`

Required fields are declared with `required: true` on the field definition. Missing required fields return `400 validation_error`.

## Type-specific time fields

Every item carries `timestamp` — the primary user-meaningful time for the item (a photo's captured-at, a note's authored-at). Type-specific time properties appear only when a type genuinely carries more than one meaningful time.

* **`core.task`** adds `due_at`, `completed_at`, optional `starts_at` — three distinct moments, none reducible to `timestamp`.
* **`core.event`** adds `starts_at` and `ends_at`.
* **`core.media.*`** adds `published_at` — the content's publication date, distinct from when the user engaged with it.

When a type has one meaningful user-time, that time is `timestamp` and the type adds no extra field. A highlight's "when-highlighted" is `timestamp`; there is no separate `highlighted_at`.

## Display hints

Types may declare an optional `display_hints` block:

```json theme={null}
"display_hints": {
  "title_field": "title",
  "body_field": "body"
}
```

This points generic readers (catalog views, search UIs, item previews) at the canonical title and body fields. Hints are inherited from the nearest ancestor when a subtype omits them.

## Merge policy

Types may declare an optional `merge_policy` block that controls how concurrent edits resolve per field. Long-text fields like `body` and `notes` typically keep both copies; scalars and metadata typically take the last writer's value.

```json theme={null}
"merge_policy": {
  "fields": {
    "body": "keep_both_copies",
    "notes": "keep_both_copies"
  },
  "default": "last_writer_wins"
}
```

The two strategies are `last_writer_wins` and `keep_both_copies`. Field names in `merge_policy.fields` must exist in the type's `fields` map. Inheritance behaves like `display_hints` and `version_policy` — a subtype's policy merges field-by-field over its parent's, and a subtype that omits the block inherits the parent's wholesale.

See [Conflicts](/concepts/conflicts) for the resolution flow and the per-core-type defaults.
