Export bundle format
A Marrow export bundle is a zip file with a transparent, human-readable structure. You can unzip it and read it without any Marrow tooling.
For why this format exists and how the guarantee is enforced, see Restore guarantee.
Export scope (v4)
Section titled “Export scope (v4)”Bundle schema v4 is the current format. This table matches the restore guarantee scope:
| Category | Included in bundle | Notes |
|---|---|---|
| Exported today (v4) | Node tree, revisions, attachments, node_properties, node_links (links.json), trash (include_trash) | Round-trips via marrow restore; CI verifies with test_round_trip.py |
| Never exported | Stars, Inbox notifications, node watches | User-scoped tables — not workspace content |
| Planned v5 | Comments, share links, folder view definitions | Not in v4 bundles yet; restore guarantee will extend when bundle v5 ships |
Search indices and other derived state are not in the bundle — they are rebuilt on restore.
File naming
Section titled “File naming”marrow-export-{workspace-slug}-{timestamp}.zip # fullmarrow-export-{workspace-slug}-slim-{timestamp}.zip # slimThe marrow restore CLI also accepts the legacy freehold-export-* prefix from bundles produced before the project rename.
Layout
Section titled “Layout”bundle.zip├── manifest.json├── nodes/│ ├── {node-id}.md│ └── {node-id}.json├── revisions/│ └── {node-id}/│ ├── {revision-id}.md│ └── {revision-id}.json├── assets/│ └── {attachment-id}{ext}└── links.jsonFolder nodes appear only in manifest.json — they have no files under nodes/. Only page-typed nodes get content files.
manifest.json
Section titled “manifest.json”Contains workspace and org metadata, all entity IDs, and the bundle schema version. Schema is currently v4 (Marrow 0.2+). Restore supports v1, v2, v3, and v4: older bundles are auto-upgraded — their legacy collection/page structure is mapped onto the new nodes tree on read.
v4 manifests carry:
- The full node tree (folders + pages), each node’s
parent_id,position(fractional index), anddeleted_at node_properties— folder property schemas and page valuesinclude_trash— whether soft-deleted nodes were included in the export
nodes/
Section titled “nodes/”Current state of every page-typed node.
{node-id}.md— Markdown render of the current revision (always present).{node-id}.json— canonical BlockNote JSON (present when the current revision is JSON-format).
The Markdown is for humans. The JSON is what gets restored byte-for-byte.
v3 bundles used a pages/ directory with the same file naming; v4 renamed it to nodes/ to match the unified node tree.
revisions/
Section titled “revisions/”The full append-only history. Each page-typed node has a subfolder containing every revision. Same .md + .json convention as nodes/.
Slim bundles omit this directory entirely. The manifest sets "slim": true and "revisions": []. Restore recreates a single revision per page from the nodes/ content.
CLI: marrow export --slim. API: ?slim=true.
Soft-deleted nodes (trash)
Section titled “Soft-deleted nodes (trash)”By default, exports omit nodes that have a deleted_at set, so a backup matches what users currently see. To include trash:
- CLI:
marrow export --include-trash - API:
?include_trash=true
The manifest records the choice ("include_trash": true|false). Restore replays each included node’s deleted_at so trash is preserved across the round trip.
assets/
Section titled “assets/”Every attachment, named by attachment ID with the original extension.
links.json
Section titled “links.json”Internal node-to-node links (internal_links), broken links (broken_links), and orphaned nodes (orphaned_nodes). Used to reconstruct cross-references on restore. The orphaned_nodes array lists nodes with no inbound links from other exported nodes.
In v4, broken_links is always [] — the export serializes only the live node_links index, which by construction contains resolvable node-to-node links, so no broken links can be recorded. The field is retained for schema stability.
Bundle schema versions
Section titled “Bundle schema versions”| Version | Introduced | Notes |
|---|---|---|
| v1 | Initial | Markdown-only revisions. |
| v2 | — | Added links.json. |
| v3 | 0.1 | Added .json files alongside .md for canonical BlockNote content; content under pages/. |
| v4 | 0.2 (current) | Collapsed collections + pages into a single nodes tree (folders + pages); content under nodes/; added parent_id, position, deleted_at, include_trash, and node_properties. |
Restore is backward-compatible: any older bundle restores cleanly into a current Marrow workspace. v1/v2/v3 bundles are auto-upgraded — their legacy collection/page layout is mapped onto the v4 node tree on read.
Inspecting a bundle
Section titled “Inspecting a bundle”unzip -l marrow-export-mydocs-20260101T120000Z.zipunzip -p marrow-export-mydocs-20260101T120000Z.zip manifest.json | jq .If you want to verify a backup is restorable without disturbing your live instance, restore it into a fresh database:
# `-v` drops the Postgres volume so the DB is genuinely empty — otherwise# restore collides on slugs that already exist in a non-empty database.docker compose down -v && docker compose up -dcd api && alembic upgrade headmarrow restore /path/to/bundle.zip