manifest.json
manifest.json is required, and a reader SHOULD be able to answer three
questions from it alone, before touching anything else: can I read this version,
what is in the file, and is any of it damaged.
{ "formatVersion": 1, "generatorApp": "StitchPencil", "generatorVersion": "0.1", "created": "2026-07-11T12:00:00Z", "title": "Koi Pond", "entries": [ { "path": "document.json", "sha256": "<hex>", "role": "document", "derived": false }, { "path": "plan.json", "sha256": "<hex>", "role": "plan", "derived": true }, { "path": "preview.png", "sha256": "<hex>", "role": "preview", "derived": true }, { "path": "references/1a2b….jpg", "sha256": "<hex>", "role": "reference", "derived": false } ]}Fields
Section titled “Fields”| Field | Type | Required | Meaning |
|---|---|---|---|
formatVersion | integer | yes | Container format version. Increases only on breaking changes. |
generatorApp | string | no | Writing application’s name. Informational. |
generatorVersion | string | no | Writing application’s version. Informational. |
created | ISO-8601 string | no | Doubles as the last-saved timestamp: the manifest is rewritten on every save. |
title | string | no | Mirrors the document title. |
entries | array | yes | One record per archive entry, see below. |
Entry records
Section titled “Entry records”| Field | Type | Required | Meaning |
|---|---|---|---|
path | string | yes | Entry name inside the archive. |
sha256 | hex string | yes | Lowercase hex SHA-256 of the entry’s uncompressed bytes. |
role | string | yes | What the entry is. Open set — see below. |
derived | boolean | yes | True if the entry can be regenerated from document.json. |
Defined roles are document, plan, preview and reference. The set is
open: readers MUST tolerate roles they do not know, and MUST NOT
fail a file because of one.
title, and why it is here
Section titled “title, and why it is here”title duplicates a value that also lives in document.json. That redundancy
is deliberate: a gallery listing fifty projects can render name, thumbnail and
date from manifest.json plus preview.png, without inflating and parsing
fifty documents. If the two disagree, document.json wins — it is the source of
truth, and the manifest copy is a cache.
Checksums and recovery
Section titled “Checksums and recovery”sha256 is computed over the entry’s uncompressed bytes. Verification is not
optional, but what a reader does on failure depends on what failed:
| Failing entry | Required behaviour |
|---|---|
document.json | The file is corrupt. Fail the load with a clear error. |
A derived entry (plan, preview) | Drop the entry, continue. It can be regenerated. |
A reference entry | Drop the image, keep the project, continue. |
This is partial recovery, and it is the point of per-entry checksums rather than one over the whole archive: a truncated preview should cost you a thumbnail, not a design.
Note that ZIP’s own CRC-32 is a separate, weaker check on a different layer. Readers MUST verify both — CRC-32 per entry as the archive is read, SHA-256 per manifest record afterwards.
Entries not listed
Section titled “Entries not listed”An archive may contain entries that the manifest does not mention. Readers MUST ignore them rather than treating the mismatch as corruption; this is how future revisions add content without breaking existing readers.
The converse — a manifest record whose entry is missing — is treated exactly like a checksum failure for that role, using the table above.