Core records

draft v0.117 records, each with a field table. Types are TypeScript-flavored and JSON-compatible.

Sister pages: fronting, optional modules.

records on this page
  1. Producer
  2. Capabilities
  3. SourceRef
  4. Privacy
  5. Warning
  6. System
  7. Member
  8. Birthday
  9. ProxyTag
  10. Group
  11. GroupMembership
  12. TaxonomyTerm
  13. TaxonomyAssignment
  14. CustomFieldDefinition
  15. CustomFieldValue
  16. Note
  17. Asset
string JSON string
number JSON number
boolean JSON true/false
null JSON null
UUID file-local id (UUIDv4/v7/ULID)
ISO8601 UTC timestamp string
Date YYYY-MM-DD
HexColor #RRGGBB
T | null nullable value
T[] array of T
Record<K, V> open object map
"a" | "b" string enum (listed inline)

Envelope helpers

Producer metadata, capabilities declaration, source references, privacy, and warnings — these can appear at the file level or on individual records.

Producer

Identifies the app that wrote the export. Sits at the top of the envelope.

FieldTypeRequiredNotes
appstringyesDisplay name of the producing app, e.g. "Prism", "Sheaf".
app_versionstringnoProducing app's release version.
exporter_versionstringnoVersion of the OpenPlural exporter implementation, if separate from the app.
app_idstringnoShort canonical app ID — see registered IDs in spec hub.
{
  "app": "Prism",
  "app_version": "3.4.0",
  "exporter_version": "0.1.0",
  "app_id": "prism"
}

Capabilities

Declares which OpenPlural modules this file populates. Lets importers know what to expect without scanning every array.

FieldTypeRequiredNotes
modulesstring[]yesSubset of: "systems", "members", "groups", "taxonomy", "custom_fields", "front_periods", "front_events", "front_comments", "notes", "assets", "chat", "boards", "relationships", "polls", "reminders", "habits", "proxy", "sharing", "safety".

SourceRef

Records the original app's identifier for a converted record. Multiple refs allowed when a record has been round-tripped between apps.

FieldTypeRequiredNotes
appstringyesRegistered app ID, e.g. "prism", "sheaf", "simply_plural", "pluralkit".
collectionstringyesSource-app collection/table name, e.g. "members", "fronts".
idstringyesSource-app primary key value (string-coerced).
uuidstring | nullnoSome apps (e.g. PluralKit) expose a separate stable UUID alongside their public ID.
"source_refs": [
  { "app": "sheaf", "collection": "members", "id": "0193..." },
  { "app": "pluralkit", "collection": "members", "id": "abcde", "uuid": "5b2..." }
]

Privacy

A conservative common privacy descriptor plus the original source detail preserved for round-trips.

FieldTypeRequiredNotes
visibility"public" | "friends" | "private" | "trusted" | "unknown"noConservative bucket. Importers without matching levels must round to the next-strictest. Sheaf maps from system.privacy directly.
sourceRecord<string, unknown>noRaw source-app privacy data — buckets, per-relation rules, scopes. Lossless preservation.

Warning

Documents loss, degradation, or skipped records. Used both in the envelope's warnings array and in importer results.

FieldTypeRequiredNotes
level"info" | "warning" | "error"yesSeverity. "error" means data was lost; "warning" means data was degraded or partially preserved.
codestringyesMachine-readable code, e.g. "module_not_supported", "field_truncated", "asset_uri_only".
record_typestring | nullnoThe OpenPlural record type or path the warning relates to.
record_idUUID | nullnoSpecific record's id if applicable.
messagestringyesHuman-readable explanation.
countnumber | nullnoIf the warning aggregates multiple records.

Systems & members

The two records every app has. Member fields overlap heavily across apps; system-level fields vary more.

System

A plurality system's profile and nesting metadata. Multiple systems may appear in one file (Lighthouse, Ampersand, and some user setups support nested systems).

FieldTypeRequiredNotes
idUUIDyesFile-local. References target for system_id on other records.
namestringyesSystem name. Sheaf: system.name. Prism: from systemSettings.systemName.
display_namestring | nullnoOptional alternate display form.
descriptionstring | nullnoMarkdown allowed.
tagstring | nullnoSystem tag/suffix. Sheaf: system.tag. PluralKit: system.tag.
colorHexColor | nullnoAccent color.
avatar_asset_idUUID | nullnoReferences an entry in assets[].
banner_asset_idUUID | nullnoReferences an entry in assets[].
parent_system_idUUID | nullnoFor nested systems (Lighthouse subsystems, Ampersand nested systems).
archivedbooleannoDefaults to false.
privacyPrivacynoSee Privacy.
settingsRecord<string, unknown>noApp-agnostic system settings; app-specific settings belong in extensions.
source_refsSourceRef[]noSee SourceRef.
extensionsRecord<string, unknown>noApp-namespaced raw data preservation.
{
  "id": "sys_01HV4Z...",
  "name": "Example System",
  "display_name": null,
  "description": "Markdown or plain text",
  "tag": "| ex",
  "color": "#66ccff",
  "avatar_asset_id": "asset_avatar",
  "banner_asset_id": null,
  "parent_system_id": null,
  "archived": false,
  "privacy": { "visibility": "private", "source": {} },
  "settings": {},
  "source_refs": [{ "app": "sheaf", "collection": "system", "id": "u_..." }],
  "extensions": {
    "sheaf": { "date_format": "ymd", "replace_fronts_default": true }
  }
}

Member

A member, alter, headmate, custom front, or equivalent profile record. Role-like data lives in taxonomy, not on this record.

FieldTypeRequiredNotes
idUUIDyesFile-local.
system_idUUIDyesReferences systems[].id.
namestring | nullnoSheaf: members[].name (decrypted in export). Optional because some apps (Octocon) allow nameless alters and synthesize a display label. Importers without name handling should derive a display string from display_name, pronouns, or a fallback.
display_namestring | nullnoSheaf: members[].display_name. Prism: headmates[].displayName.
pronounsstring | nullnoFree text — no enum because every app uses free text.
descriptionstring | nullnoBio. Markdown allowed if extensions.openplural.markdown_fields says so.
agestring | nullnoFree text (some apps store ranges, "ageless", etc.).
birthdayBirthday | nullnoSee Birthday.
colorHexColor | nullno
avatar_asset_idUUID | nullnoReferences assets[]. Prism: headmates[].profilePhotoData becomes an Asset.
banner_asset_idUUID | nullno
proxy_tagsProxyTag[]noSee ProxyTag. PluralKit primary source.
is_custom_frontbooleannoDefaults to false. Used for Simply Plural and Ampersand custom fronts.
archivedbooleannoDefaults to false.
created_atISO8601 | nullnoSheaf: members[].created_at. Prism: headmates[].createdAt.
sort_ordernumber | nullnoLower = earlier in the list. Prism: displayOrder.
privacyPrivacyno
source_refsSourceRef[]no
extensionsRecord<string, unknown>no
{
  "id": "mem_01HV4Z...",
  "system_id": "sys_01HV4Z...",
  "name": "Alex",
  "display_name": "Alex",
  "pronouns": "they/them",
  "description": "Bio",
  "age": null,
  "birthday": { "value": "2001-04-29", "precision": "day", "year_visible": true },
  "color": "#88ccaa",
  "avatar_asset_id": "asset_avatar",
  "banner_asset_id": null,
  "proxy_tags": [{ "prefix": "a:", "suffix": null }],
  "is_custom_front": false,
  "archived": false,
  "created_at": "2026-04-29T18:00:00Z",
  "sort_order": 0,
  "privacy": { "visibility": "private", "source": {} },
  "source_refs": [{ "app": "sheaf", "collection": "members", "id": "0193..." }],
  "extensions": {}
}

Birthday

Birthdays vary by precision: full date, month-day only, year-only, or with year hidden. Sub-record on Member.

FieldTypeRequiredNotes
valuestringyesDate in YYYY-MM-DD, --MM-DD, YYYY, or YYYY-MM form depending on precision.
precision"day" | "month" | "year" | "month_day"yesGranularity actually known.
year_visiblebooleannoIf false, importers should hide the year in UI even though it's stored. Defaults to true.

ProxyTag

PluralKit-style proxy tag. Sub-record on Member.

FieldTypeRequiredNotes
prefixstring | nullnoMatch prefix, e.g. "a:".
suffixstring | nullnoMatch suffix.

Organization

Groups when you want hierarchy or folders. Taxonomy when you want flat labels — roles, tags, sources, relationship categories.

Group

Hierarchical member organization. Sheaf groups, Prism member groups, Simply Plural groups, PluralKit (flat) groups all map here.

FieldTypeRequiredNotes
idUUIDyes
system_idUUIDyes
namestringyes
descriptionstring | nullno
colorHexColor | nullno
emojistring | nullnoSingle emoji. Prism: memberGroups.emoji.
parent_group_idUUID | nullnoSheaf: groups[].parent_id. Prism: memberGroups.parentGroupId. PluralKit groups are flat — leave null.
sort_ordernumber | nullno
source_refsSourceRef[]no
extensionsRecord<string, unknown>no

Sheaf mapping: Sheaf inlines member_ids on each group. Map those into GroupMembership records — one per member_id — instead of duplicating onto the group.

GroupMembership

Normalized many-to-many edge between groups and members.

FieldTypeRequiredNotes
idUUIDyes
group_idUUIDyesReferences groups[].id.
member_idUUIDyesReferences members[].id.
sort_ordernumber | nullnoPosition within the group.
source_refsSourceRef[]no

TaxonomyTerm

Reusable label of a given kind — a role, tag, source, relationship, etc. Created once, attached many times via assignments.

FieldTypeRequiredNotes
idUUIDyes
system_idUUIDyes
kindstringyesSee enum below.
namestringyes
descriptionstring | nullno
colorHexColor | nullno
parent_term_idUUID | nullnoFor nested taxonomies (e.g. tag categories).
source_refsSourceRef[]no
extensionsRecord<string, unknown>no
Recommended kind values "role" | "tag" | "source" | "relationship" | "identity" | "topic" | "status" | "custom" | "unknown"

Sheaf mapping: Sheaf tags[] become kind: "tag" terms. Sheaf inlines member_ids per tag — those become TaxonomyAssignment records with subject_type: "member".

TaxonomyAssignment

Attaches a term to a subject record (member, note, asset, etc.).

FieldTypeRequiredNotes
idUUIDyes
term_idUUIDyesReferences taxonomy_terms[].id.
subject_type"member" | "note" | "asset" | "front_period" | "custom"yesImporters must skip assignments whose subject_type they don't support and emit a warning.
subject_idUUIDyes
scope"profile" | "appearance" | "session" | "custom" | nullnoOptional context — e.g. a role applies to a member's profile, vs. just a particular front.
source_refsSourceRef[]no
extensionsRecord<string, unknown>no

Custom fields

Splitting definitions from values lets importers type-check before assigning, and lets one definition cover any number of subjects.

CustomFieldDefinition

A custom field's name, type, and metadata. Sheaf, Prism, Simply Plural, Ampersand all expose this concept.

FieldTypeRequiredNotes
idUUIDyes
system_idUUIDyes
namestringyesDisplay name.
field_typestringyesSee enum below.
optionsstring[] | Record<string, unknown> | nullnoFor select/multiselect. Sheaf stores options as JSONB dict | None, so OpenPlural accepts either a string array or an object/record verbatim.
supports_markdownbooleannoHint to importers for text/markdown rendering.
date_precision"day" | "month" | "year" | "month_day" | nullnoFor date/date_range types. Prism: customFields.datePrecision.
sort_ordernumber | nullnoSheaf: custom_fields[].order.
privacyPrivacynoSheaf: custom_fields[].privacy.
source_refsSourceRef[]no
extensionsRecord<string, unknown>no
Recommended field_type values "text" | "markdown" | "number" | "boolean" | "color" | "date" | "date_range" | "timestamp" | "month" | "year" | "month_year" | "month_day" | "select" | "multiselect" | "json"

CustomFieldValue

A single value for one definition + one subject. Sheaf nests these inside the definition; OpenPlural keeps them in a sibling array for normalization.

FieldTypeRequiredNotes
idUUIDyes
field_idUUIDyesReferences custom_fields[].id.
subject_type"member" | "system"yesFor v0.1, only members and the system itself. Future expansion possible.
subject_idUUIDyes
valueunknownyesType matches the field's field_type. For multiselect use a string array; for date_range use { start, end }; for json use the original payload.
source_refsSourceRef[]no
extensionsRecord<string, unknown>no

Content

Two records do all the work for free-form text and media: Note for journals and member notes, Asset for anything binary.

Note

Member notes, journal entries, communal journal entries, and dated entries. Maps cleanly from Prism notes, Simply Plural notes, Plural Star journals, Lighthouse journals, Sheaf journals, Ampersand journal posts.

FieldTypeRequiredNotes
idUUIDyes
system_idUUIDyes
member_idUUID | nullnoPrimary subject member for per-member entries. Null for system-wide entries; communal or multi-member journals may need a future explicit subject-members field.
titlestring | nullno
bodystringyesMarkdown unless flagged otherwise.
created_atISO8601yes
updated_atISO8601 | nullno
entry_dateDate | nullnoFor journal-style day entries (Plural Star, Lighthouse).
author_member_idsUUID[]noCo-authored entries (Sheaf journal frozen-author snapshots, Ampersand multi-author).
colorHexColor | nullnoPrism: notes.color.
visibility"private" | "system" | "friends" | "trusted" | "public" | nullno
pinnedbooleanno
content_warningstring | nullno
attachment_asset_idsUUID[]noInline images/files via Asset.
source_refsSourceRef[]no
extensionsRecord<string, unknown>no

Open design question: Note currently collapses member notes, per-member journals, and communal journals into one record, with member_id meaning the primary subject member and author_member_ids meaning who wrote it. That's enough for Sheaf's current journal export, but apps like Lighthouse and Octocon suggest a possible future additive field such as subject_member_ids if multi-member journal scoping needs to become first-class. Splitting journals into a wholly separate core record looks less attractive than making note subject-scoping more explicit.

Asset

An image, file, or media blob. Records reference assets by ID instead of embedding bytes inline. Either inline data_base64/data_uri or external uri.

FieldTypeRequiredNotes
idUUIDyes
kind"avatar" | "banner" | "image" | "audio" | "video" | "file" | "thumbnail" | "unknown"yes
mime_typestring | nullno
file_namestring | nullno
uristring | nullnoExternal URL. Importers should treat as fragile.
data_base64string | nullnoBase64-encoded payload (no data URI prefix).
data_uristring | nullnoFull data:<mime>;base64,<...>.
size_bytesnumber | nullno
sha256string | nullnoHex digest. Recommended for dedupe.
widthnumber | nullnoPixels.
heightnumber | nullnoPixels.
duration_msnumber | nullnoFor audio/video.
source_refsSourceRef[]no
extensionsRecord<string, unknown>noE.g. Prism's blurhash, waveform, encryption metadata.

An asset must populate at least one of uri, data_base64, or data_uri. Files with only uri should emit a "warning" with code "asset_uri_only" on export so importers know the asset isn't self-contained.

Continue to fronting for front periods, events, comments, and assignments — or optional modules for chat, polls, and the importer contract.