Skip to content

Export format reference

A notebook export is one canonical index.json and the content-addressed assets declared by that index. Python, browser, agent, and custom clients consume the same durable relation between exported states and named outputs.

Directory layout

text
report/
  index.json
  assets/
    <sha256>.npy
    <sha256>.arrow
    <sha256>.output.json
    <sha256>.cell.json
    <sha256>.bin

index.json is the single entry point. Asset paths are derived from codec and SHA-256 rather than supplied as arbitrary paths.

An export directory may contain application-owned sidecar files beside index.json. The declared export closure consists of index.json and the exact files referenced under assets/. Repository generations apply a stricter private directory closure before reuse.

Index schema

The root object contains exactly:

FieldContract
schemaExact value marimo-export.export.v1
spec_sha256SHA-256 of canonical ExportSpec bytes
default_stateFingerprint of the default normalized state
notebookNotebook filename and document SHA-256
producermarimo version, marimo-export version, implementation SHA-256
inputsOrdered input definition names
control_bindingsScoped UI object IDs mapped to inputs and semantic paths
outputsOrdered published output names
aliasesAuthored state names mapped to state fingerprints
statesNonempty mapping from state fingerprints to complete state data

The index is canonical UTF-8 JSON. Object keys, number spelling, string encoding, and scalar tags must match the canonical producer representation byte for byte.

Each root input name is an opaque, nonempty string of at most 255 UTF-8 bytes. ExportSpec and notebook inspection validate Python identifiers before export production. Durable readers preserve the written names across Unicode database versions.

control_bindings is the browser event-routing authority for prepared UI controls. Each binding contains a root input name and a typed path through that input's control tree. The input value must exactly match one root input name. Root controls use an empty path. Sequence children append an index step, mapping children append a key step, and wrapper or form children append an element step. Multiple scoped IDs may carry the same binding when one source control appears in several projections. Consumers use these records directly and do not parse projection-scoped object IDs.

State entries

Each state key is the lowercase SHA-256 over canonical JSON for its inputs. Each state entry contains exactly:

FieldContract
inputsComplete object with the exact root input-name set
outputsObject with the exact root output-name set

Every alias targets a declared state. Several aliases may target the same fingerprint when authored rows normalize to the same complete vector. One output name keeps one codec and media type across every state. default_state references one declared fingerprint.

Output descriptors

Every descriptor contains:

  • codec
  • media type
  • provenance
  • inline scalar or JSON value, or an asset reference

The producer implementation SHA-256 identifies the exact installed marimo-export Python source set that created the index. Capture freezes this identity before execution and commits it after the end-of-operation identity check succeeds.

Descriptor provenance contains the stored value's python_type. For an exporter-backed output, that type is marimo_export.outputs.BlobAsset, not the selected notebook value's original type. Native marimo cache keys and return references remain inside the producer process.

BlobAsset descriptors also record filename and portable metadata.

The descriptor shape is closed by codec:

Descriptor kindExact fields
Scalar or portable JSONcodec, media_type, provenance, value
Rendered output, complete cell, NumPy, or Arrowcodec, media_type, provenance, asset
BlobAssetcodec, media_type, provenance, asset, filename, metadata

provenance contains exactly python_type. asset contains exactly sha256 and size. Readers reject missing and unknown descriptor fields.

Native codecs

Export format version 1 accepts seven codecs:

CodecRequired media typeStored formAsset suffix
marimo.scalar.v1application/vnd.marimo.scalar.v1+jsonInline scalarNone
marimo.json.v1application/vnd.marimo.json.v1+jsonInline portable JSONNone
marimo.output.v1application/vnd.marimo.output.v1+jsonCanonical rendered-output snapshot.output.json
marimo.cell.v1application/vnd.marimo.cell.v1+jsonCanonical complete-cell snapshot.cell.json
numpy.npy.v1application/x-npyNumPy NPY file.npy
apache.arrow.file.v1application/vnd.apache.arrow.fileArrow IPC file or stream framing.arrow
marimo.blob-asset.msgpack.v1Valid BlobAsset media type from descriptorCanonical MessagePack BlobAsset.bin

The codec identifies the native envelope. A BlobAsset media type identifies the representation within that envelope. Custom media types extend the output space while the codec set remains closed for version 1.

Scalar wire values

JSON-compatible scalars remain inline. Values that JSON cannot preserve use these closed tagged objects:

json
{ "type": "bigint", "value": "9007199254740992" }
{ "type": "float", "value": "nan" }
{ "type": "float", "value": "infinity" }
{ "type": "float", "value": "-infinity" }
{ "type": "float", "value": "negative-zero" }

The tagged bigint decimal has no leading plus sign or unnecessary leading zeroes and must lie outside the JavaScript safe-integer range. Untagged numbers must be finite, with integers inside that range.

Readers reject unknown or noncanonical scalar tags.

JSON values

marimo.json.v1 stores any portable JSON value inline in the descriptor. Objects, arrays, strings, booleans, finite numbers in the JavaScript-safe range, and null retain their JSON shape. Python returns a frozen value through ExportOutput.json(). Browser clients load it through jsonLoader().

Rendered-output snapshots

marimo.output.v1 stores the following shape. Whitespace is added for display. Stored bytes use canonical JSON:

json
{
  "schema": "marimo.output.v1",
  "projectionSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "ownerCellId": "cell-id",
  "output": {
    "channel": "output",
    "mimetype": "text/markdown",
    "data": "<span>Ready</span>"
  },
  "resources": {
    "files": {},
    "modelNotifications": [],
    "functions": {
      "cell-id-projection-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-ui-cell-id-ui": []
    },
    "uiValues": {
      "cell-id-projection-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-ui-cell-id-ui": 3
    }
  }
}

output is null when the formatted value has no terminal output. The record is inert. ownerCellId identifies the authored source cell whose UI object graph produced the output.

Complete-cell snapshots

marimo.cell.v1 stores the following shape. Whitespace is added for display. Stored bytes use canonical JSON:

json
{
  "schema": "marimo.cell.v1",
  "projectionSha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "cell": {
    "id": "cell-id",
    "name": "summary",
    "codeSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "config": { "disabled": false }
  },
  "outcome": "completed",
  "output": {
    "channel": "output",
    "mimetype": "text/plain",
    "data": "42"
  },
  "console": [{ "channel": "stdout", "mimetype": "text/plain", "data": "ready\n" }],
  "resources": {
    "files": {},
    "modelNotifications": [],
    "functions": {},
    "uiValues": {}
  }
}

cell.name is null for an unnamed cell. output is null when the completed cell has no terminal output. console preserves the ordered marimo console records captured during the selected cell's fresh execution.

Both snapshot records use the same replay resources:

  • files maps each closed virtual resource to a slash-prefixed /@file/ path and data URL. Model notifications retain marimo's trusted relative ./@file/ URL, which resolves through that normalized key.
  • modelNotifications contains the reachable AnyWidget model lifecycle closure in replay order. Model IDs contain the record's projectionSha256 and are scoped by planned output.
  • functions maps every projection-scoped UI object ID to an empty array. A live Python function makes the snapshot nonportable. The producer removes a form's inert validate registration when should-validate is false.
  • uiValues maps every projection-scoped registry-owned UI object ID to its accepted frontend value after state updates.

The producer applies the same scope to UI object IDs, random IDs, HTML attributes, and structured UI references within one snapshot. Each UI ID begins with the snapshot's ownerCellId or cell.id, preserving marimo's ownership authorization, and ends in a projection-root structural path. Common controls keep the same ID when a conditional tree adds or removes siblings. Model IDs use their projection-scoped model namespace. This lets a consumer merge rendered-output and complete-cell resources from the same live UI element.

Browser loaders validate and freeze these records. Rendering and model replay belong to the consuming application.

The marimo snapshot reference defines every record, message union, replay-resource field, and browser parser.

Asset identity

An asset reference contains its lowercase SHA-256 and byte size. Equal (codec, SHA-256) identities share one asset across states. Reused identities must agree on every descriptor fact.

Producer and local-reader bounds are:

  • 64 MiB per asset
  • 512 MiB across unique assets in one export

Browser callers can apply their own per-output and aggregate limits through the browser API.

The Python reader's 512 MiB closure limit includes index.json plus unique declared assets. Descriptor records can represent assets up to 2,147,483,647 bytes so another format implementation can apply its own lower operational limit.

BlobAsset envelope

A native BlobAsset MessagePack envelope contains exactly:

  • data: representation bytes
  • media_type: validated media type
  • filename: optional portable basename
  • metadata: portable JSON object

The envelope's media type, filename, and metadata must agree with the index descriptor before a reader returns the representation.

Verification

Readers verify:

  • canonical index bytes
  • exact input and output key sets
  • state fingerprints
  • representation consistency across states
  • asset path identity
  • declared and observed byte length
  • SHA-256
  • NPY or Arrow framing
  • canonical BlobAsset envelope
  • descriptor and envelope agreement
  • complete declared asset closure

The loaded index.json is the integrity root. Verification establishes consistency with that index. It does not authenticate who produced the index. Python NotebookExport.identity and browser NotebookExport.identity expose the lowercase SHA-256 of the exact canonical index.json bytes.

Protocol limits

ValueLimit
Canonical index.json16 MiB and 2,000,000 JSON values
Input, output, alias, and producer version name255 UTF-8 bytes
Control binding path256 typed steps
Media type1,024 printable ASCII bytes
BlobAsset metadata256 KiB of canonical portable JSON
Python producer or local-reader asset64 MiB
Python export closure512 MiB including index.json

The browser errors and limits reference lists browser defaults and caller overrides.

HTTP delivery

Serve index.json and each declared asset at byte-stable HTTP or HTTPS URLs. Content encoding is permitted because browser readers verify the decoded body. Use immutable caching for content-addressed assets. A replaceable index.json needs a revalidation policy that matches the application's freshness contract.

Cross-origin applications need an explicit Cross-Origin Resource Sharing (CORS) policy. The browser API accepts a custom fetch implementation for credentials and request policy.

Consumer behavior

Opening an export reads and validates index.json. Assets remain lazy until a consumer reads one output or verifies the complete export.

State selection supports:

  • the declared default state
  • authored state alias
  • exact complete input vector
  • sparse patch from an existing state

Resolution returns an exported state already present in the export.

Versioning

The schema identifier and codec identifiers version durable behavior. A reader rejects an unknown export schema or native codec. Custom representations should use versioned media types so producer and consumer changes remain explicit.

Consume an export provides Python, browser, and agent workflows. Output representations maps built-in and custom representations to their consumers.

Released under the Apache 2.0 License.