Spec hub

draft v0.1What lives on this page: data conventions, the top-level envelope, and the shared fragments (source_refs, privacy, warnings) that show up on most records.

Field-by-field detail lives on three subpages:

Data conventions

Six rules. Skim once; they're assumed everywhere else.

JSON canonical

The interchange format is JSON. Not doing protobuf or CBOR in v0.1 — every researched app already speaks JSON, nothing else does.

UTC timestamps

Timestamps are ISO-8601 in UTC. Apps with source timezones can preserve them in extensions.

File-local IDs

Every record has a file-local id. Prefer UUIDv7 or ULID; importers must not assume an algorithm.

Source preservation

Original IDs go in source_refs. App-specific fields go in namespaced extensions.

Modules are optional

Importers should accept any subset of modules. The capabilities.modules array declares what a file populates.

Loss is reported

Skipped or degraded data must surface in warnings at export time and in the import result at import time.

Top-level envelope

What goes at the top of every file. Producer info, a capabilities array so importers don't have to scan, and the slots that hold each module's records.

Core arrays systems, members, groups, group_memberships, taxonomy_terms, taxonomy_assignments, custom_fields, custom_field_values, notes, assets.
Fronting arrays front_periods, front_events, front_comments.
Optional modules chat, boards, relationships, polls, reminders, habits, proxy, sharing, safety.
Preservation source_refs, extensions, warnings, raw app IDs.
{
  "openplural_version": "0.1",
  "exported_at": "2026-04-29T18:00:00Z",
  "producer": {
    "app": "Sheaf",
    "app_version": "1.4.2",
    "exporter_version": "0.1.0",
    "app_id": "sheaf"
  },
  "capabilities": {
    "modules": [
"systems", "members", "groups", "taxonomy",
"custom_fields", "front_periods", "notes", "assets"
    ]
  },
  "systems": [],
  "members": [],
  "groups": [],
  "group_memberships": [],
  "taxonomy_terms": [],
  "taxonomy_assignments": [],
  "custom_fields": [],
  "custom_field_values": [],
  "front_periods": [],
  "front_events": [],
  "front_comments": [],
  "notes": [],
  "assets": [],
  "chat": null,
  "extensions": {},
  "warnings": []
}

Shared record fragments

These can appear on any record. They're the main mechanism for safe conversion and future recovery. Full field tables in records.

FragmentShapePurpose
source_refsSourceRef[]Retains original app IDs for reconciliation, round-trips, dedupe, and import auditing.
extensionsRecord<string, unknown>Preserves source-specific fields without forcing every app to understand them.
privacyPrivacyA conservative common privacy level plus the original source detail.
warningsWarning[]Documents skipped, degraded, preserved-only, or repaired data.

Registered app IDs

Use these short IDs in SourceRef.app and extensions namespaces.

prism, sheaf, simply_plural, pluralkit, octocon, plural_star, lighthouse, openselves, ampersand, pluralspace.

New IDs are registered by PR to the OpenPlural repo — maintainers keep the canonical list. Apps that want a private namespace without registration can use reverse-DNS keys (e.g. com.example.app) inside extensions instead. The openplural namespace is reserved for spec-level extensions.