MDZip (.mdz) File Format Specification
Version: 1.2.0
Status: Final
Date: 2026-10-13
Table of Contents
- Overview
- Goals and Non-Goals
- Terminology
- File Format
- Archive Structure
- Manifest File
- Markdown Content Files
- Asset Files
- Linking and References
- MIME Type and File Extension
- Versioning
- Compatibility Notes
- Security Considerations
- Conformance
- Examples
- Implementation Guidance (Non-Normative)
- Future Extensions (Non-Normative)
1. Overview
The MDZip format (.mdz) is a portable document format that packages one or more Markdown content files together with their associated assets—such as images, stylesheets, and other referenced resources—into a single ZIP archive.
An .mdz file lets authors distribute a Markdown-based document together with the resources it needs, so the references between them don't break. An archive is self-contained when it includes every resource required to render it; links outside the archive (Section 9.4) and external URLs make it depend on things it does not carry. How faithfully an archive renders also depends on the Markdown dialects and asset formats the consumer supports (Section 12.4), and including a resource such as a stylesheet does not by itself define how a consumer applies it.
This specification follows established standards patterns by using RFC 2119 normative language, Semantic Versioning, and UTF-8/JSON conventions compatible with other open document and text-based standards (such as CommonMark, EPUB, and TOML ecosystems).
2. Goals and Non-Goals
Goals
- Provide a simple, open, and inspectable container format for Markdown documents.
- Let documents and the resources they use travel together as a single unit.
- Support multiple Markdown pages/sections within a single archive.
- Stay readable and inspectable with any tool that understands ZIP and Markdown, even one that knows nothing about MDZip (it may not know the entry point, mode or extensions, but the content is never locked away).
- Remain human-inspectable using any standard ZIP utility.
Non-Goals
- This specification does not mandate a specific Markdown dialect or rendering engine.
- This specification does not define Digital Rights Management (DRM) or encryption.
- This specification does not define a network transport protocol.
- This specification does not define ordered navigation, pagination, or table-of-contents structure for multi-page documents. Linking between pages is left to the document author using standard Markdown relative links.
3. Terminology
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.
| Term | Definition |
|---|---|
| Archive | The .mdz file itself; a ZIP-format container. |
| Entry point | The primary Markdown file a reader or tool opens first. |
| Manifest | The manifest.json file at the root of the archive describing document metadata and entry-point overrides. Optional for archive validity; conforming producers always write one (see Section 14.1). |
| Support file | A root-level Markdown file that describes or accompanies the archive rather than being document content: README, AGENTS, LICENSE, CHANGELOG, or CONTRIBUTING (see Section 5.5). |
| Asset | Any non-Markdown file in the archive that the archive uses: referenced by a Markdown file (images, stylesheets), by the manifest (such as cover), or by an extension or profile the archive declares (schemas, data files, templates). Also called a resource. |
| Conforming producer | Software that writes .mdz files — creating new archives or modifying existing ones — according to this specification. |
| Conforming consumer | Software that reads and/or processes .mdz files according to this specification. |
| Mode | The declared interpretation intent of an archive's contents, specified via manifest.mode. Controls how consumers render and navigate content. |
| Document mode | A mode in which the archive represents one logical publication, opened at its entry point. The default when no mode is declared. |
| Project mode | A mode in which the archive represents a collection of independently addressable Markdown documents. Requires an explicit manifest declaring "mode": "project". |
4. File Format
An .mdz file is a ZIP archive as defined by the ZIP Application Note (APPNOTE.TXT) version 6.3.10 or later.
Requirements:
- The archive MUST be a valid ZIP file.
- The archive MUST NOT use ZIP64 extensions unless a ZIP field cannot represent its value without them (for example, an entry or the archive exceeds 4 GB, or the archive has more than 65,535 entries).
- Files inside the archive MUST use UTF-8 encoded file paths. Paths may use characters from any writing system (Unicode script), such as Latin, Cyrillic, Han or Arabic; producers SHOULD write them in Unicode Normalization Form C (NFC). See Section 5.4.1.
- Producers MUST set the UTF-8 flag (general purpose bit 11) on every entry whose name contains non-ASCII characters. Consumers SHOULD decode entry names as UTF-8 even when the flag is not set, because many ZIP tools write UTF-8 names without it. When an entry's name in the central directory differs from the name in its local header, consumers MUST use the central directory.
- File path separators inside the archive MUST be forward slashes (
/). - Each entry MUST be stored without compression (method 0) or compressed with DEFLATE (method 8). Conforming consumers MUST support both methods, and MAY reject an archive that uses any other method (
ERR_ZIP_INVALID). - The archive MUST NOT use password protection or encryption.
- Every entry MUST be a regular file or a directory. Producers MUST NOT write symbolic links or other special files, such as an entry whose external file attributes give a Unix file type of symbolic link (
S_IFLNK). See Section 13.1 for how consumers treat them. - Line endings: producers SHOULD write LF (
\n) in Markdown files andmanifest.jsonthat they create or modify, and consumers MUST accept both LF and CRLF (\r\n). See Section 12.2.
5. Archive Structure
An .mdz file is a ZIP archive containing at least one Markdown file that can be resolved as the primary document using the entry point discovery algorithm defined in Section 5.5. A manifest.json file at the archive root is OPTIONAL for an archive to be valid, so a folder zipped by hand and renamed to .mdz remains a valid archive; conforming producers, however, MUST write one (see Section 14.1). All other files are organized relative to the archive root.
5.1 Required Files
There are no unconditionally required files beyond the archive being a valid ZIP. However, the archive MUST contain at least one Markdown file that satisfies the entry point discovery rules in Section 5.5.
5.2 Optional Files
| Path | Description |
|---|---|
index.md |
Conventional entry point Markdown file (see Section 5.5). |
manifest.json |
Document manifest (see Section 6). Optional for archive validity; written by every conforming producer. |
README.md |
Optional human-facing guidance for environments without full .mdz tooling (see Section 5.6). |
AGENTS.md |
Optional guidance for AI agents and tools without native .mdz support (see Section 5.8). |
5.3 Recommended Layout
Document Mode
document.mdz (ZIP archive)
├── index.md # Recommended: conventional entry point
├── manifest.json # Metadata and entry point (always written by conforming producers)
├── README.md # Optional: human-facing usage notes
├── AGENTS.md # Optional: guidance for AI agents and tools
├── chapter-01.md # Additional Markdown files (optional)
├── chapter-02.md
├── images/ # Asset directories at archive root
│ ├── cover.png
│ └── diagram.svg
└── styles/
└── style.css
Project Mode
Project archives SHOULD organize Markdown files into subdirectories by section or topic. Asset directories live at the archive root for small projects; larger projects MAY scope assets per section when it adds clarity.
Flat assets (small to medium projects):
project.mdz (ZIP archive)
├── manifest.json # Required: must declare "mode": "project"
├── index.md # Entry point: overview or home page
├── README.md # Optional: human-facing usage notes
├── AGENTS.md # Optional: guidance for AI agents and tools
├── guide/
│ ├── intro.md
│ ├── setup.md
│ └── advanced.md
├── reference/
│ ├── api.md
│ └── cli.md
├── images/ # Shared asset directories at archive root
│ ├── logo.png
│ └── banner.png
└── styles/
└── style.css
Per-section assets (larger projects):
project.mdz (ZIP archive)
├── manifest.json # Required: must declare "mode": "project"
├── index.md # Entry point: overview or home page
├── README.md # Optional: human-facing usage notes
├── guide/
│ ├── intro.md
│ ├── setup.md
│ └── images/ # Section-scoped assets
│ └── setup-screenshot.png
├── reference/
│ ├── api.md
│ └── images/
│ └── architecture.svg
└── styles/ # Shared styles at archive root
└── style.css
5.4 Path Constraints
Note for authors: These constraints apply to ZIP entry names inside the archive — the internal paths used to store files. They do not apply to relative links within your Markdown content (e.g.,
[next chapter](guide/chapter-02.md)or), which follow normal Markdown and relative URL conventions. Authors using a standard tool to create.mdzfiles will never need to think about this — the tool and the operating system handle it. This section is aimed at implementors building producers or consumers in code.
The ZIP format permits entry names that are unsafe or non-portable across operating systems. MDZip imposes tighter constraints to ensure archives are safe to extract and consistent across platforms.
- All entry paths inside the archive MUST be relative to the archive root.
- Entry paths MUST NOT begin with a leading slash (
/). - Entry paths MUST NOT contain
..segments (e.g.,../escape.mdordocs/../index.md). A..inside a name, as innotes..md, is not a segment and is allowed. This applies to the stored names only: links in Markdown may use../, including to leave the archive (Section 9.4). - Entry paths MUST NOT contain empty segments (as in
docs//intro.md) or.segments (as in./index.mdordocs/./intro.md). Each entry therefore has exactly one stored form, and lookups never need to normalize path syntax. - A file entry's path MUST NOT end in
/. An entry whose name ends in/is a directory entry: directory entries are optional, carry no content, are never files for entry point discovery or link resolution, and MUST otherwise satisfy these rules. - An archive MUST NOT contain a file whose path is also a directory of another entry, such as a file
docstogether withdocs/intro.mdor a directory entrydocs/, compared after NFC normalization. - Entry paths MUST NOT contain null bytes or ASCII control characters (U+0000–U+001F, U+007F).
- Entry paths MUST NOT contain characters reserved by common operating systems:
\,:,*,?,",<,>,|. - An archive MUST NOT contain two entries whose paths are equal after NFC normalization (Section 5.4.1).
- An archive SHOULD NOT contain two entries whose paths differ only in case (for example
Index.mdandindex.md), because they collide when extracted to a case-insensitive filesystem such as the defaults on Windows and macOS. For this check, "differ only in case" means equal under Unicode default case folding (CaseFolding.txt, statuses C and F).
Uniqueness and conflicts are checked after NFC normalization, the only normalization applied to stored paths.
These rules make entry names safe, not universally extractable. A valid name can still be unusable on some filesystems — for example Windows reserved device names such as CON.md, or names ending in a space or period. Consumers that read entries directly from the archive are unaffected; consumers that extract to a filesystem SHOULD report names the target filesystem cannot represent rather than renaming them silently (Section 13.1).
See Section 13.1 for consumer requirements on rejecting non-conforming paths.
Tip for authors: names are matched case-sensitively (Section 5.4.1), so a link must use the same capitalization as the file it points to. Windows and macOS ignore case by default, so a mismatch such as
Images/Logo.pngforimages/logo.pngcan work on the author's computer and fail everywhere else. Lowercase names make this mistake less likely, but any capitalization is valid.
5.4.1 Path Matching
Whenever a consumer looks up an archive entry by path — manifest.entryPoint, manifest.cover, index.md in entry point discovery, or a relative link (Section 9.3) — it MUST compare paths as follows:
- Normalize both the path being looked up and each entry path to Unicode Normalization Form C (NFC).
- Compare the normalized strings exactly, code point by code point. Matching is case-sensitive:
Index.mddoes not matchindex.md.
Normalization makes visually identical paths match regardless of how they were encoded; for example, é stored as one code point (U+00E9) and as e followed by a combining acute accent (U+0065 U+0301), as some macOS tools write it, are the same path. Case-sensitive comparison keeps matching identical in every implementation and language. Case-insensitive matching is avoided because case conversion depends on language: Turkish I lowercases to dotless ı, German ß uppercases to SS, and Greek has two lowercase forms of sigma.
The only case-insensitive comparisons in this specification are for fixed ASCII names: the Markdown extensions .md and .markdown (Section 7.3) and the support file names in Section 5.5. These MUST be compared ASCII case-insensitively, treating only A–Z as equal to a–z. Implementations MUST NOT use locale-sensitive case conversion for them; for example, lowercasing LICENSE with Turkish rules yields lıcense, which would fail to match.
When a lookup fails only because of case (the paths are equal under Unicode default case folding, as in Section 5.4), a consumer SHOULD report the near match to the user (for example, "did you mean index.md?"), because this is usually an authoring mistake that went unnoticed on a case-insensitive filesystem. It MUST NOT silently use the near match.
5.5 Entry Point Discovery
Conforming consumers MUST determine the primary Markdown file using the following ordered algorithm:
If
manifest.jsonis present at the archive root and declares anentryPoint:- If the value is not a valid entry point path (defined below), the archive is non-conforming; the consumer SHOULD report
ERR_ENTRYPOINT_INVALID. - Otherwise, if it matches a file in the archive (Section 5.4.1), use that file.
- Otherwise, the archive is non-conforming; the consumer SHOULD report
ERR_ENTRYPOINT_MISSING.
In both non-conforming cases, the consumer MUST NOT silently fall back to rules 2–4. It MAY offer to continue with rules 2–4 as an explicit recovery, provided it shows the user a visible warning that the declared entry point is invalid or missing.
Rules 2–4 apply only when the manifest is absent or declares no
entryPoint.- If the value is not a valid entry point path (defined below), the archive is non-conforming; the consumer SHOULD report
If
index.mdexists at the archive root, use it.Consider the
.mdand.markdownfiles at the archive root, setting aside support files (defined below):- If exactly one Markdown file remains, use it.
- If none remain and exactly one root-level Markdown file exists (that is, the archive's only root-level Markdown file is a support file), use that file.
Otherwise, the consumer MUST NOT silently select a file arbitrarily. The consumer SHOULD present the user with a list of available Markdown files to choose from, or report a clear error indicating that no unambiguous entry point could be determined.
Valid entry point path. For rule 1, an entryPoint value is valid when it is a non-empty string that:
- is a relative archive path satisfying Section 5.4 (no leading
/; no empty,.or..segments apart from the one leading./allowed below; forward slashes only; no control or reserved characters); - names a file, not a directory (it does not end in
/); - has a
.mdor.markdownextension (ASCII case-insensitive, Section 5.4.1).
A single leading ./ is permitted and is removed before matching. A value that is valid but does not match any entry, including one that differs from an entry only in case, is missing rather than invalid.
Support files. For rule 3, a support file is a root-level file whose name, compared ASCII case-insensitively (Section 5.4.1), is one of the following, with a .md or .markdown extension:
READMEAGENTSLICENSECHANGELOGCONTRIBUTING
For example, README.md, readme.markdown and AGENTS.MD are support files; docs/README.md (not at the root) and README-draft.md are not. The list is closed: consumers MUST NOT treat other files as support files, so that every consumer resolves the same entry point. Future versions of this specification may extend it.
Support files describe or accompany the archive (see Section 5.6 and Section 5.8), so adding one to an archive that has a root-level content file does not change which file rule 3 selects. For example, an archive with no manifest and no index.md containing notes.md and AGENTS.md resolves to notes.md. (An archive whose only root-level Markdown files are support files, such as README.md and AGENTS.md together, has no unambiguous entry point under rule 3; a producer adding the second one is therefore required to add a manifest, see Section 14.1.)
Conforming producers MUST declare the entry point in manifest.json (see Section 14.1), which satisfies rule 1 and makes resolution unambiguous across all consumers. Rules 2–4 exist so that archives created without a conforming producer, such as a folder zipped by hand, still resolve predictably.
5.6 Human-Facing README.md (Optional)
Producers SHOULD include a root-level README.md when practical, especially for files likely to be opened by recipients without .mdz-aware tooling.
Because README.md is inside the archive, producers distributing .mdz to likely unaware recipients SHOULD provide a primary discovery method outside the archive. Examples include:
- a download-page note describing
.mdzand basic opening steps, - a companion sidecar text file next to the
.mdzfile, - a website/help link shown wherever the file is shared.
When present, README.md SHOULD briefly include:
- what the archive is (
.mdz/ MDZip), - where primary document content begins (for example,
index.md), - how to open the package with standard tools (for example, unzip then open Markdown files),
- where to find format documentation (for example, a project URL).
Producers MAY include instructions for opening the package with a specific consumer tool. When they do, they SHOULD also include a generic fallback workflow (for example, unzip and open index.md) so the package remains understandable without that specific tool.
README.md is a secondary aid and does not replace out-of-archive discovery guidance for unaware recipients. It is informational only and does not affect conformance logic. It is a support file, so it does not count toward rule 3 of entry point discovery (Section 5.5) unless it is the archive's only root-level Markdown file.
A reusable sample is available at examples/templates/README.sample.md.
5.7 Mode Semantics
An .mdz archive has an interpretation mode that tells conforming consumers how to render and navigate its contents. Mode is either applied by default or explicitly declared via manifest.mode. Mode values are case-sensitive and MUST be lowercase.
5.7.1 Mode Defaults
If manifest.json is absent, or if manifest.json is present but does not include a mode field, consumers MUST treat the archive as mode: "document". This is a default, not an inference — the rule is deterministic and requires no examination of archive contents.
The number of Markdown files present, their names, or the directory structure of the archive MUST NOT influence mode selection.
5.7.2 Document Mode
document mode indicates that the archive represents one logical publication, regardless of how many Markdown files it contains.
Consumers MUST:
- Resolve the entry point using the standard entry point discovery algorithm (Section 5.5)
- Open the entry point as the document's initial content
Links from the entry point to other Markdown files in the archive are ordinary links (Section 9). They do not imply inclusion, concatenation or reading order; following one may replace or supplement the current view. This specification defines no content-inclusion mechanism.
Consumers MAY:
- Present the entry point and the Markdown files it links to as one unified view
- Produce a single rendered output from them (for example, for printing or export)
This is the default mode and applies when no manifest is present or when manifest.mode is "document".
5.7.3 Project Mode
project mode indicates that the archive represents a collection of independent Markdown documents with navigational relationships between them.
Requirements:
- A
manifest.jsonfile MUST be present - The manifest MUST declare
"mode": "project" - The manifest SHOULD define a valid
entryPoint(conforming producers MUST; see Section 14.1)
Consumers MUST:
- Treat each Markdown file as a separate document that can be addressed and opened on its own
- NOT flatten or merge Markdown files into a single logical document
Consumers MAY:
- Provide navigation UI (for example, a sidebar or table of contents)
- Support cross-document linking
- Allow users to open any file directly, not only the entry point
Note on navigation structure: This specification does not define ordered navigation, pagination, or table-of-contents layout.
projectmode establishes that documents MUST be preserved as separate units; it does not mandate how consumers surface navigation to users.
5.7.4 Entry Point Semantics by Mode
In document mode:
- Entry point is resolved using the standard discovery algorithm (Section 5.5)
In project mode:
- If
manifest.entryPointis present, it MUST be used as the primary entry point, subject to rule 1 of Section 5.5 when the value is invalid or matches no file - If
manifest.entryPointis absent, consumers MUST apply the standard discovery algorithm - Consumers MUST NOT arbitrarily select a file when no unambiguous entry point can be determined
5.7.5 Unrecognized Mode Values
If manifest.mode contains a value not recognized by the consumer (for example, a value defined in a future spec version), the consumer:
- MUST NOT silently fall back to
documentmode - SHOULD fail with error
ERR_MODE_UNSUPPORTEDand report the unrecognized value - MAY offer the user a choice to proceed in
documentmode as an explicit override
This preserves the spec's principle that consumers MUST NOT rely on heuristics to infer structure.
5.7.6 Consumer Support Levels
Consumers MAY support one or both modes:
- Document-only consumers MUST support
documentmode. When encounteringprojectmode, they MAY fall back todocumentmode provided they emit a clear warning thatprojectmode is not supported and the archive will be treated as a single document. They MAY instead fail withERR_MODE_UNSUPPORTED. Silent fallback without any warning is NOT permitted. - Full-featured consumers SHOULD support both modes.
A consumer MUST NOT silently render an archive incorrectly when it encounters a mode it cannot handle. For unrecognized mode values (values not defined in this spec), consumers MUST NOT fall back silently and SHOULD fail with ERR_MODE_UNSUPPORTED. For recognized but unsupported modes (e.g. a document-only consumer encountering project), warn-and-fallback to document is conforming provided the warning is user-visible; the consumer need not ask the user before falling back.
5.8 Agent-Facing AGENTS.md (Optional)
Producers MAY include a root-level AGENTS.md addressed to AI agents and other automated tools that encounter the archive without native .mdz support. Where README.md (Section 5.6) tells a person how to open the package, AGENTS.md tells a tool how to handle it safely.
When present, AGENTS.md SHOULD briefly include:
- what the archive is (
.mdz/ MDZip) and where to find format documentation, - a preference for archive-aware access (for example, an MDZip MCP server, library or compatible tool) over extracting or rewriting the archive with generic ZIP tools,
- how to locate the entry point (the algorithm in Section 5.5, including that support files are set aside),
- how to interpret
manifest.mode(Section 5.7), - that the archive should not be modified with generic ZIP tools without the user's agreement: they don't apply MDZip's rules (such as keeping the entry point resolvable) and can't detect a conflicting edit made in another tool.
AGENTS.md is advisory: nothing in it overrides manifest.json, which is authoritative when present, or the rules of this specification. Like README.md, AGENTS.md does not affect conformance logic, and it is a support file for entry point discovery (Section 5.5).
A reusable sample is available at examples/templates/AGENTS.sample.md.
6. Manifest File
The manifest file, when present, MUST be named manifest.json and placed at the root of the archive. It MUST be a valid JSON document encoded in UTF-8.
The manifest requirement depends on role:
- Conforming producers MUST write
manifest.jsonin every archive they create, with at least the fields listed in Section 14.1. This applies to software only; a person zipping a folder by hand is not a producer. - Conforming consumers MUST NOT require
manifest.json. An archive without one is valid and is resolved with the entry point discovery algorithm (Section 5.5) and the defaults in Section 5.7.1 and Section 11.3.
Producer tools MAY accept non-JSON authoring inputs (for example, JSON5) as a local convenience, but conforming .mdz archives MUST contain manifest.json serialized as standard JSON per RFC 8259.
A versioned JSON Schema companion for this release is available at schema/manifest-1.2.0.schema.json. It validates manifests written by a conforming producer, so it requires spec.version and entryPoint and checks that entryPoint is a valid entry point path (Section 5.5); consumers MUST NOT use it to reject archives. Schemas for earlier releases remain in schema/. The prose specification remains normative when differences exist.
6.1 Schema
{
"spec": {
"name": "mdzip-spec",
"version": "<spec version>"
},
"producer": {
"application": {
"name": "<application/tool name>",
"version": "<application version>",
"url": "<application URL>"
},
"core": {
"name": "<core library/runtime name>",
"version": "<core library/runtime version>",
"url": "<core library/runtime URL>"
}
},
"author": {
"name": "<author name>",
"email": "<author email>",
"url": "<author URL>"
},
"title": "<document title>",
"mode": "<interpretation mode>",
"entryPoint": "<path to entry point markdown file>",
"language": "<BCP 47 language tag>",
"description": "<short description>",
"version": "<document version>",
"created": {
"when": "<RFC 3339 datetime>",
"by": {
"name": "<creator name>",
"email": "<creator email>",
"url": "<creator URL>"
}
},
"modified": {
"when": "<RFC 3339 datetime>",
"by": {
"name": "<modifier name>",
"email": "<modifier email>",
"url": "<modifier URL>"
}
},
"license": "<SPDX license identifier or URL>",
"keywords": ["<keyword>"],
"cover": "<path to cover image asset>"
}
created and modified each take one of two forms:
- an object with a required
whenand an optionalby— the current form, which producers SHOULD write; or - a plain datetime string — the legacy form from draft
1.0.x.
Datetimes in both forms MUST use the RFC 3339 date-time format, the profile of ISO 8601 used by JSON Schema — for example 2026-03-08T14:30:00Z or 2026-03-08T09:30:00-05:00. A value in another ISO 8601 form is invalid and, like other descriptive fields, ignored (Section 6.4).
Consumers MUST accept both forms, whatever the archive's spec.version, including when it has none.
Required fields: none for arbitrary hand-authored manifests.
For conforming producer-generated manifests,spec.versionandentryPointare REQUIRED, andtitleandmodeare RECOMMENDED (see Section 14.1).
6.2 Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
spec |
object | OPTIONAL | Container for spec compatibility metadata. |
spec.name |
string | OPTIONAL | Identifier for the target specification (e.g., "mdzip-spec"). |
spec.version |
string | CONDITIONALLY REQUIRED | The version of this specification the file conforms to. MUST be a Semantic Versioning 2.0.0 string (e.g., "1.2.0"). REQUIRED when manifest.json is emitted by a conforming producer. |
producer |
object | OPTIONAL | Producer provenance metadata. |
producer.application |
object | OPTIONAL | User-facing tool/application that generated the archive. |
producer.application.name |
string | OPTIONAL | Application/tool display name. |
producer.application.version |
string | OPTIONAL | Application/tool version string. |
producer.application.url |
string | OPTIONAL | URL for producer application homepage/repository. Producers SHOULD include when stable and available. |
producer.core |
object | OPTIONAL | Underlying reusable core library/runtime used by the producer application. |
producer.core.name |
string | OPTIONAL | Core library/runtime name. |
producer.core.version |
string | OPTIONAL | Core library/runtime version string. |
producer.core.url |
string | OPTIONAL | URL for core library/runtime homepage/repository. Producers SHOULD include when stable and available. |
author |
object | OPTIONAL | Human/content author attribution metadata. |
author.name |
string | OPTIONAL | Author display name. |
author.email |
string | OPTIONAL | Author contact address. Producers SHOULD include when available and appropriate to disclose. |
author.url |
string | OPTIONAL | Author website/profile URL. Producers SHOULD include when available and appropriate to disclose. |
title |
string | OPTIONAL | The human-readable title of the document. If present, it MUST NOT be empty. If omitted, consumers SHOULD derive a display title from available context (for example, the entry-point, filename or first heading). Conforming producers SHOULD include it. |
mode |
string | OPTIONAL | The interpretation mode of the archive. Allowed values: "document" (default), "project". Values are case-sensitive and MUST be lowercase. If absent, consumers MUST assume "document". If present with an unrecognized value (including incorrect casing such as "Document" or "PROJECT"), consumers MUST NOT silently fall back to document mode. MUST be "project" when the archive is a collection of independent documents (Section 5.7.3); a single publication split across several files is "document". Conforming producers SHOULD include it explicitly, including "document". See Section 5.7. |
entryPoint |
string | CONDITIONALLY REQUIRED | Path to the primary Markdown file, relative to the archive root (e.g., "chapters/start.md"). MUST be a valid entry point path as defined in Section 5.5 (a relative .md/.markdown file path satisfying Section 5.4), and the referenced file MUST exist in the archive, matched as described in Section 5.4.1. Consumers report a malformed value as ERR_ENTRYPOINT_INVALID and a valid but absent one as ERR_ENTRYPOINT_MISSING. REQUIRED when manifest.json is emitted by a conforming producer. If omitted, consumers apply the entry point discovery algorithm defined in Section 5.5. |
language |
string | OPTIONAL | The natural language of the document as a BCP 47 language tag (e.g., "en", "fr-CA"). If omitted, the language is unspecified: consumers MAY fall back to a default of their choice, such as the user's interface language or "en", but SHOULD NOT state it as the document's language (for example, when exposing it to assistive technology). |
description |
string | OPTIONAL | A short plain-text description of the document. |
version |
string | OPTIONAL | The version of the document itself (not the spec version). SHOULD follow Semantic Versioning. |
created |
object or string | OPTIONAL | Creation metadata: an object with when (current form) or an RFC 3339 datetime string (legacy draft 1.0.x form); consumers MUST accept both (Section 6.1). |
created.when |
string | CONDITIONALLY REQUIRED | Required when created is an object. The creation datetime, in RFC 3339 date-time format. |
created.by |
object | OPTIONAL | Creator identity metadata when known and appropriate to disclose. |
created.by.name |
string | OPTIONAL | Creator display name. |
created.by.email |
string | OPTIONAL | Creator contact address. |
created.by.url |
string | OPTIONAL | Creator website/profile URL. |
modified |
object or string | OPTIONAL | Last-modified metadata: an object with when (current form) or an RFC 3339 datetime string (legacy draft 1.0.x form); consumers MUST accept both (Section 6.1). |
modified.when |
string | CONDITIONALLY REQUIRED | Required when modified is an object. The last-modified datetime, in RFC 3339 date-time format. |
modified.by |
object | OPTIONAL | Modifier identity metadata when known and appropriate to disclose. |
modified.by.name |
string | OPTIONAL | Modifier display name. |
modified.by.email |
string | OPTIONAL | Modifier contact address. |
modified.by.url |
string | OPTIONAL | Modifier website/profile URL. |
license |
string | OPTIONAL | An SPDX license identifier (e.g., "MIT", "CC-BY-4.0") or a URL pointing to the license text. |
keywords |
array of strings | OPTIONAL | A list of keywords or tags describing the document. |
cover |
string | OPTIONAL | Archive-root-relative path to a cover image asset (e.g., "images/cover.png"). It MUST be a relative archive path satisfying Section 5.4 that names a file (no trailing /); as with entryPoint, a single leading ./ is permitted and removed before matching (Section 5.4.1). It MUST reference an existing file in the archive. If cover is invalid or the referenced file is missing, conforming consumers SHOULD ignore cover and continue processing the archive; they MAY emit a warning. |
6.3 Additional Fields
Conforming producers MAY include additional fields not defined in this specification. Conforming consumers MUST ignore any unrecognized fields.
A producer that rewrites an existing manifest.json MUST preserve every field it does not recognize, unless the user explicitly asks for it to be removed. This applies at the top level and inside recognized objects (for example, an unrecognized property within producer or author), and to every JSON value type: object, array, string, number, boolean and null. Fields may have been added by a newer version of this specification, a profile or another tool; dropping them silently loses data the producer cannot see.
Preservation is semantic, not textual: the preserved value MUST be equal, as JSON, to the original, but whitespace, indentation and property order may change. A producer that deserializes the manifest into a closed, typed model MUST retain unrecognized data alongside it (or merge it back before saving), rather than serializing only the fields it knows.
6.4 Manifest Validation
A consumer that finds manifest.json validates it as follows.
Unreadable manifests. If the manifest is not valid UTF-8, is not valid JSON (RFC 8259), or its top-level value is not an object, it is invalid. The consumer MUST report ERR_MANIFEST_INVALID and MUST NOT silently ignore the manifest. It MAY offer to continue as if the archive had no manifest, after a visible warning.
Fields. The fields defined in Section 6.2 are either critical, because they change how the archive is interpreted, or descriptive. A field present with the wrong type or an invalid value is handled by kind:
| Kind | Fields | When the value is wrong |
|---|---|---|
| Critical | spec, spec.version, mode, entryPoint |
An error; consumers MUST NOT ignore it. A spec that is not an object, or a spec.version that is not a Semantic Versioning string, is ERR_MANIFEST_INVALID. A mode that is a string the consumer doesn't recognize is ERR_MODE_UNSUPPORTED (Section 5.7.5); any other non-string mode is ERR_MANIFEST_INVALID. An invalid entryPoint is ERR_ENTRYPOINT_INVALID (Section 5.5). |
| Descriptive | Every other field in Section 6.2, for example title, author, language, created and cover |
Consumers SHOULD ignore the field and MAY warn; the archive still opens. |
null is a wrong type, not an absent field: {"spec": null} is invalid. Unrecognized fields are never validated (Section 6.3). A missing cover file is tolerated while a missing entryPoint file is not for the same reason: cover is descriptive, entryPoint is critical.
Duplicate member names. Two members with the same name in one object (for example two "title" members) make the manifest ambiguous, and RFC 8259 leaves their meaning undefined. Producers MUST NOT write duplicate member names. Consumers SHOULD report them; a consumer that accepts such a manifest MUST use the last occurrence, which is what common JSON parsers do.
6.5 Example Manifests
Document Mode (default — mode may be omitted in hand-authored manifests; producers should include it)
{
"spec": {
"name": "mdzip-spec",
"version": "1.2.0"
},
"producer": {
"application": {
"name": "mdzip-cli",
"version": "1.2.0",
"url": "https://example.com/mdzip-cli"
},
"core": {
"name": "mdzip-core",
"version": "0.9.4",
"url": "https://example.com/mdzip-core"
}
},
"author": {
"name": "Jane Smith",
"email": "jane@example.com",
"url": "https://example.com/~jane"
},
"title": "My Technical Guide",
"mode": "document",
"entryPoint": "index.md",
"language": "en",
"description": "A comprehensive guide to building widgets.",
"version": "2.1.0",
"created": {
"when": "2026-01-15T09:00:00Z",
"by": {
"name": "Jane Smith",
"email": "jane@example.com",
"url": "https://example.com/~jane"
}
},
"modified": {
"when": "2026-03-08T14:30:00Z",
"by": {
"name": "Jane Smith",
"email": "jane@example.com",
"url": "https://example.com/~jane"
}
},
"license": "CC-BY-4.0",
"keywords": ["widgets", "guide", "tutorial"],
"cover": "images/cover.png"
}
Project Mode
{
"spec": {
"name": "mdzip-spec",
"version": "1.2.0"
},
"producer": {
"application": {
"name": "mdzip-cli",
"version": "1.3.0",
"url": "https://example.com/mdzip-cli"
}
},
"title": "My Documentation Site",
"mode": "project",
"entryPoint": "index.md",
"language": "en"
}
7. Markdown Content Files
7.1 Encoding
All Markdown files inside the archive MUST be encoded in UTF-8.
7.2 Markdown Dialect
This specification does not mandate a specific Markdown dialect. Producers SHOULD note in the manifest description or within the document itself which dialect is used (e.g., CommonMark, GitHub Flavored Markdown).
7.3 File Extensions
Markdown files SHOULD use the .md file extension. The .markdown extension is also permitted. Consumers MUST recognize both extensions ASCII case-insensitively (so NOTES.MD is a Markdown file); see Section 5.4.1.
7.4 Multiple Pages
An .mdz archive MAY contain multiple Markdown files, allowing a document to be split across chapters, sections, or pages. The primary document is the entry point, determined by the discovery algorithm in Section 5.5 (by convention index.md). Other Markdown files SHOULD be linked from the primary document or from other Markdown files within the archive using relative paths.
8. Asset Files
Asset files are the non-Markdown files the archive uses (see the definition in Section 3), such as images, stylesheets, fonts, data files, schemas or templates. A file can be used without any Markdown link to it — a cover image, or a data file read by an extension.
8.1 Placement
Assets SHOULD be organized in named subdirectories at the archive root, grouped by type (e.g., images/, styles/, fonts/, pdf/). Additional hierarchy within those directories is RECOMMENDED when the volume of files warrants it. A dedicated assets/ parent directory MAY be used to group all asset directories together, but is not required.
8.2 Allowed Asset Types
There is no restriction on asset file types.
8.3 Unused Assets
Conforming producers SHOULD NOT include files the archive does not use: files that nothing in the archive references and that no declared extension needs, such as leftovers from an editor or build. A file that is not linked from Markdown is not unused for that reason alone. Producers modifying an archive keep files they don't recognize (Section 14.1); deciding what is unused is a creation-time choice.
9. Linking and References
9.1 Internal Links
References to other files within the archive (Markdown files and assets) MUST use relative paths based on the location of the referencing file within the archive.
Relative links MAY include ../ segments. A link whose resolved path stays within the archive root is an internal link; one that climbs above the archive root is a link outside the archive (Section 9.4).
Example: A Markdown file at the archive root (index.md) referencing an image:

Example: A Markdown file in a subdirectory (chapters/intro.md) referencing an image at the archive root:

9.2 External Links
Links to external URLs (e.g., https://example.com) are permitted. Conforming consumers MAY choose to warn users before following external links. A destination with a URL scheme (such as https: or mailto:) is an external URL and is not resolved as a path.
A document can also load an external resource automatically, such as an image written . Fetching it tells that server the document was opened, and the archive is no longer self-contained (Section 1): the resource can change or disappear, and it does not load offline.
- Consumers SHOULD let the user turn off automatic loading of external resources.
- Consumers SHOULD NOT automatically load external resources over unencrypted
http:. - Producers and validators MAY report external resources as informational notices, as for links outside the archive (Section 9.6), not as errors.
9.3 Path Resolution
This section and Sections 9.4–9.5 apply to standard Markdown link destinations: inline and reference-style links and images ([text](path), , [text][ref] with [ref]: path) and the href and src attributes of HTML elements embedded in Markdown. Other link syntaxes, such as wikilinks, are covered by Section 9.7.
Conforming consumers MUST resolve relative paths against the path of the referencing file within the archive, using this algorithm:
- Split the destination at its first
#. What follows is the fragment: it identifies an anchor in the target document, does not take part in path resolution, and keeps any?it contains (page.md#heading?texthas the fragmentheading?text). Then remove any?queryfrom the part before the#. A query has no meaning in this specification; consumers MAY keep it for presentation but MUST NOT use it to choose the target. - If what remains is empty (
#usage,?view=print), the link refers to the referencing document itself. - If the destination begins with
/or contains\, it is not a valid relative link; treat it as unresolved. - Split the destination into segments on
/, then percent-decode each segment as UTF-8, exactly once; for example,Mary%20Shelley/Frankenstein.mdhas the segmentsMary ShelleyandFrankenstein.md, and%252e%252edecodes to the literal name%2e%2e, not to... A segment with invalid percent-encoding (%ZZ, or bytes that are not valid UTF-8 such as%C3%28) makes the link unresolved. A segment that decodes to text containing/,\or a control character makes the link unresolved, so an encoded separator such as%2Fcannot introduce a path step. A decoded#or?(from%23or%3F) is part of the name, not a delimiter. - Starting from the directory of the referencing file, apply the segments in order:
.(or one that decodes to.) and an empty segment (from//) stay in the current directory,..(or one that decodes to..) moves up one directory, and any other segment moves into that name. A destination that ends in/names a directory: inside an archive, where entries are files, it is unresolved; outside one, it is resolved only where Section 9.5 allows a link to end at an archive. - If every step stays within the archive, the result is an internal link: match the resulting path against archive entries as described in Section 5.4.1.
- If a
..step would move above the archive root, the link leaves the archive and is resolved as described in Section 9.4.
The base is always the referencing file's location: an HTML <base> element in a document does not change how its links resolve.
This specification does not define how a heading becomes an anchor. Markdown dialects differ (## My Heading is #my-heading on GitHub and #My Heading in Obsidian), so whether a fragment matches a heading depends on the consumer's dialect (Section 12.4). A fragment that matches no anchor still leaves the link resolved to its target document.
Link resolution is separate from archive entry path validation. The archive's own entry names MUST still satisfy Section 5.4 — an entry named ../escape.md remains invalid — and nothing in this section relaxes the extraction rules in Section 13.1.
A link that cannot be resolved MUST be treated as unresolved: the consumer MAY indicate it to the user (for example, by styling it as a broken link) but MUST NOT substitute a different target. In particular, a consumer MUST NOT clamp a .. step at the archive root and resolve the rest of the link inside the archive.
9.4 Links Outside the Archive (Optional)
A relative link can climb above the archive root to reach files stored next to the .mdz file, for example to link a packaged document to other notes in the same folder. For resolution, the archive root is treated as a virtual directory located where the .mdz file is: the directory containing the archive is its parent.
Example: a folder MyVault/ contains Notes.md, References/Design.md and Project.mdz, and the archive contains index.md and chapters/intro.md.
| Referencing file | Link | Target |
|---|---|---|
index.md |
../Notes.md |
MyVault/Notes.md |
index.md |
../References/Design.md |
MyVault/References/Design.md |
chapters/intro.md |
../../Notes.md |
MyVault/Notes.md |
chapters/intro.md |
../index.md |
index.md, inside the archive (an internal link) |
Rules:
- Support is OPTIONAL. A consumer that does not support links outside the archive MUST treat them as unresolved. Supporting them does not affect any other conformance requirement.
- A consumer that supports them resolves the remaining segments against the directory containing the
.mdzfile, using the host's normal path and filesystem rules. Path matching on the host follows the host filesystem (which may ignore case or normalize Unicode); Section 5.4.1 governs matching only inside archives. - If the consumer has no host filesystem location for the archive — for example, the archive was opened from bytes in a web browser, an email attachment or a download stream — links outside the archive MUST be treated as unresolved, not mapped to a guessed location.
- If the target is outside the consumer's trust boundary (Section 9.6), does not exist, or the host denies access, the link is unresolved.
- If a segment of the remaining path names an MDZip archive, the link enters that archive and is resolved as described in Section 9.5.
These links apply to any link destination — documents, images and other assets — but resources that a document loads automatically (such as ) are unresolved by default (Section 9.6).
9.5 Links Into Archives (Optional)
A relative link can address a document inside an .mdz archive by treating the archive as a directory, both from an ordinary Markdown file next to the archive and from a link that has left another archive (Section 9.4). Continuing the example in Section 9.4, from MyVault/Notes.md:
[Design notes](Project.mdz/chapters/intro.md)
[Introduction section](Project.mdz/chapters/intro.md#background)
[Project](Project.mdz)
Rules:
- Support is OPTIONAL. A consumer that does not support links into archives MUST treat them as unresolved.
- While resolving on the host, a segment whose name has an
.mdzextension (ASCII case-insensitive) becomes an archive boundary only when it names an existing file that the consumer can open as an MDZip archive. A directory that happens to be namedSomething.mdzis an ordinary directory. - The segments after the boundary form a path inside that archive. Apply
.and..within it; a..above that archive's root makes the link unresolved. Match the result against the archive's entries as described in Section 5.4.1. - A link that ends at the archive itself (
Project.mdz, optionally with a trailing/) opens the document selected by entry point discovery (Section 5.5); a fragment then applies to that document. - A link MUST NOT enter more than one archive boundary. Leaving the referencing archive (Section 9.4) does not count as entering one, but a link that names an archive inside another archive (such as
Outer.mdz/inner.mdz/index.md) is unresolved. - A link that enters the archive containing the referencing file (for example,
../Project.mdz/chapters/intro.mdfrom insideProject.mdz) refers to the same entry as the corresponding internal link. - Links into archives use ordinary relative Markdown links; this specification defines no URI scheme for them. Renderers that do not support them will show these links as broken.
When a consumer needs an identifier for a document inside an archive, for example to build backlinks or a link graph, it SHOULD use the archive's host path followed by the entry path, such as MyVault/Project.mdz/chapters/intro.md. This identifies the document at its current location on the host; it changes when the archive or its folder is moved or renamed.
9.6 Security and Portability of Links Beyond an Archive
An archive may come from anyone, so a link that leaves it is a request, written by the archive's author, to reach files on the reader's machine. The risk is disclosure as well as modification: an untrusted archive can try to display or reveal local files, for example with .
- Resolving or following a link outside an archive or into another archive is read-only. It MUST NOT extract files to the host filesystem, write to it, or modify any archive.
- Trust boundary. A consumer that supports these links MUST limit them to a trust boundary: a location the user has chosen to trust, such as the workspace or vault an editor has open. A target outside it MUST be treated as unresolved, whether or not it exists, unless the user explicitly allows access to that target (for example by confirming a prompt that names it). A consumer with no such location, such as a viewer opening a single file, has no trusted targets outside the archive. The host's own access controls apply as well.
- Navigation and automatic loading. Navigation (the user follows a link) and automatic loading (the consumer fetches a resource the document displays, such as an image, stylesheet or embedded file) are different uses. Automatic loading from outside the archive MUST be off by default. A consumer MAY enable it for targets inside the trust boundary when the user or the host's configuration asks for it, and MUST NOT enable it for targets outside the boundary.
- Consumers SHOULD show the user where a navigation link outside the archive leads before opening it, unless the target is inside the trust boundary.
- Archives that rely on links outside the archive are not self-contained: those links resolve only while the archive sits next to the files it references, and break when the archive is moved, shared or opened in a browser. Authors opt into this deliberately; content that must travel with the document belongs inside the archive.
- Producers MUST NOT rewrite links outside the archive into internal links, or vice versa, without the author's request. Validators and producers SHOULD report links outside the archive as informational notices (for example, "this archive links to 3 files outside it"), not as errors.
9.7 Other Link Syntaxes
This specification does not prohibit or restrict other hyperlink syntaxes. Markdown dialects and host applications define their own, such as wikilinks ([[Page]], [[Page#Heading|label]]) and embeds (![[image.png]]) in Obsidian, Foam and similar tools. Archives MAY contain them, and consumers MAY support them using the resolution rules of the dialect or host that defines them, including looking up a page by name rather than by path. Sections 9.3–9.5, including the rule against substituting a different target, govern only standard Markdown link destinations.
For links in these syntaxes:
- Consumers that support a syntax that looks pages up by name SHOULD consider the documents inside the archive before looking further, so that links between an archive's own documents work the same in every host that supports the syntax.
- Consumers that don't support a syntax SHOULD leave its text intact, both when displaying it (show it as written rather than dropping it) and when saving (Section 14.1 already requires untouched content to be preserved).
- The security and portability rules of Section 9.6 still apply when such a link reaches outside the archive: resolving or following it is read-only and stays within the host's trust boundaries.
These syntaxes are dialect-specific, and a renderer that doesn't know them shows them as plain text. Producers relying on them SHOULD say so, as described in Section 12.4; producers that need links to work everywhere SHOULD use standard Markdown links.
10. MIME Type and File Extension
| Property | Value |
|---|---|
| File extension | .mdz |
| MIME type | Proposed application/vnd.mdzip |
Producers and consumers SHOULD use the .mdz file extension. The proposed MIME type application/vnd.mdzip SHOULD be used when the format is transmitted over HTTP or identified in metadata.
application/vnd.mdzip is currently an unregistered vendor media type in this specification. Until formal registration is completed, implementations MAY treat it as provisional for interoperability testing. For broad interoperability, servers MAY use application/zip or application/octet-stream as a fallback until application/vnd.mdzip is more widely recognized.
11. Versioning
11.1 Specification Versioning
This specification uses Semantic Versioning 2.0.0:
- Major version increments indicate breaking, backwards-incompatible changes to the format.
- Minor version increments indicate backwards-compatible additions or clarifications.
- Patch version increments indicate corrections or editorial changes with no impact on format compatibility.
Compatibility is defined in terms of archives, not implementations. Within a major version, an archive valid under an earlier minor version stays readable by a consumer of a later one, and an archive written to a later minor version stays usable by an earlier consumer, which ignores what it doesn't recognize. A minor version can add requirements on implementations (for example, 1.2.0 requires producers to write a manifest), so software that conforms to one minor version does not automatically conform to the next.
11.2 Manifest spec.version Field
When manifest.json includes spec.version, it identifies the version of this specification that the producing tool targeted. Conforming consumers:
- MUST reject files where
spec.versionmajor version is higher than the highest major version the consumer supports. - SHOULD warn users when
spec.versionmajor version is lower than the consumer's current major version support. - MUST NOT reject a file solely because its
spec.versionminor or patch version differs from the consumer's supported version, provided the major version is supported. This does not promise that every feature renders; an archive can still fail for other reasons, such as an unsupported mode (Section 5.7.6).
11.3 Manifests Without Version Metadata
Conforming consumers MUST NOT assume that all manifests are producer-generated. If spec.version is absent (whether manifest.json is absent or present but missing spec.version), conforming consumers:
- MUST treat the archive as compatible with baseline 1.x behavior defined by this specification.
- MUST NOT reject the archive solely due to missing version metadata.
- SHOULD expose that version metadata is unavailable when presenting document details to users.
12. Compatibility Notes
This section summarizes expected behavior that improves interoperability across tools, platforms, and Markdown ecosystems.
12.1 Baseline Interoperability
- Conforming consumers MUST implement the entry point discovery algorithm defined in Section 5.5.
- Conforming consumers MUST support UTF-8 encoded Markdown and JSON files.
- Conforming consumers MUST support relative path resolution semantics defined in Section 9.
12.2 Line Endings
- Conforming producers SHOULD write LF (
\n) line endings in Markdown files andmanifest.jsonthat they create or modify, converting CRLF (\r\n) from the source content if needed. Markdown treats both as line endings, so the conversion does not change the document's meaning, and no warning is required. - Conforming producers SHOULD NOT change line endings in other files unless the user asks. Some formats depend on them, for example CRLF in Windows batch files or in CSV files that follow RFC 4180.
- Conforming producers MUST NOT change line endings in entries they do not otherwise modify (Section 14.1).
- Conforming producers MAY offer an option to keep the original line endings.
- Conforming consumers MUST accept text files using LF (
\n) and CRLF (\r\n) line endings.
This specification does not define a manifest field for line endings: consumers accept both, and line endings can differ from file to file. Tools that extract archives MAY offer to convert line endings for the target platform.
12.3 Path and Case Behavior
- Paths inside archives MUST use forward slashes (
/), regardless of host platform. - Consumers MUST match archive paths case-sensitively after NFC normalization, as described in Section 5.4.1, for deterministic behavior across operating systems and languages.
- Unknown manifest fields MUST be ignored to preserve forward compatibility.
12.4 Markdown Dialect Portability
- Producers SHOULD prefer portable Markdown constructs that degrade gracefully in CommonMark-class renderers.
- Producers SHOULD document non-portable syntax expectations when relying on dialect-specific features, such as wikilinks or other link syntaxes (Section 9.7).
13. Security Considerations
Conforming consumers and producers should treat .mdz content as potentially untrusted input. The following controls are recommended to reduce risk.
13.1 Archive Handling
- Consumers MUST reject archive entries whose paths would resolve outside the archive root after normalization (e.g., absolute paths or relative paths that escape the root), and MUST NOT write any file outside the intended destination when extracting. This concerns entry names inside the ZIP; Markdown links that point outside the archive are resolved separately and never trigger extraction (Section 9.4, Section 9.6).
- Entries whose paths violate Section 5.4 are non-conforming. Consumers MUST NOT extract them under their stored names, and MUST NOT silently rename them or resolve a link to a renamed copy, since renaming changes an entry's identity and can create new collisions. Consumers SHOULD reject such an archive (
ERR_PATH_INVALID); they MAY offer an explicit recovery, such as skipping the entries or extracting them under names the user approves. - Consumers SHOULD enforce limits on extracted entry count, total uncompressed bytes, and compression ratio to mitigate ZIP bomb attacks.
- Consumers MUST NOT follow a symbolic link entry (Section 4): they MUST NOT create it as a link when extracting, and MUST NOT resolve a path or a Markdown link through it, even when its target lies inside the archive. They SHOULD report it, and MAY skip it or read it as an ordinary file whose content is the stored target text. The same applies to hard links and other special files if the ZIP library exposes them.
- Consumers MUST NOT choose between two entries whose paths are equal after NFC normalization, or between a file and a directory with the same path (Section 5.4). They SHOULD reject the archive with
ERR_DUPLICATE_PATHorERR_PATH_CONFLICTrespectively. - Consumers extracting to a filesystem that is case-insensitive or normalizes Unicode SHOULD detect entries that would collide there and report them, rather than letting one silently overwrite another.
13.2 Content Handling
- Consumers MUST NOT execute scripts or binaries from
.mdzcontent as part of normal rendering. - Consumers SHOULD warn before opening external URLs found in document links.
- Consumers SHOULD let users turn off automatic loading of external resources such as remote images, which reveals to their servers that the document was opened (Section 9.2).
- Consumers SHOULD apply implementation-specific size/time limits while parsing large Markdown or metadata files.
13.3 Producer Guidance
- Producers SHOULD NOT include credentials, private keys, or other secrets in archive content.
- Producers SHOULD include only files the archive uses (Section 8.3), so that unrelated local files are not shared by accident.
14. Conformance
14.1 Conforming Producer
A conforming producer is any software that writes .mdz files. Each of its operations is either a creation (writing a new archive, for example by packing a folder or converting a document) or a modification (changing an existing archive and saving it). A creation MUST satisfy the list below. A modification MUST satisfy the second list; the archive it writes MUST also meet item 1 below, and every entry it adds or rewrites items 7–13.
When creating an archive, a conforming producer:
- MUST produce a valid ZIP archive as defined in Section 4.
- MUST include a
manifest.jsonat the archive root (Section 6). - MUST include
spec.versionin the manifest. - MUST include
entryPointin the manifest, referencing a Markdown file present in the archive. This satisfies rule 1 of the entry point discovery algorithm (Section 5.5), so the entry point is unambiguous for every consumer. - SHOULD include
titleandmodein the manifest. When the archive is in project mode, MUST setmodeto"project"(Section 5.7.3). - SHOULD name the entry point
index.mdat the archive root when the content allows, as the most widely recognized convention. - MUST use UTF-8 encoding for all Markdown and JSON files.
- SHOULD write LF (
\n) line endings in Markdown files andmanifest.jsonthat it creates or modifies, and SHOULD NOT change line endings in other files unless the user asks (Section 12.2). - MUST use forward-slash path separators in the archive.
- MUST NOT include
..segments in any entry path (Section 5.4). - MUST NOT include null bytes, ASCII control characters, or OS-reserved characters (
\,:,*,?,",<,>,|) in any entry path. - SHOULD write file paths, and the paths in
entryPointandcover, in Unicode NFC. MUST NOT write two entries whose paths are equal after NFC normalization, empty or.segments, or a file whose path is also a directory, and SHOULD NOT write two entries whose paths differ only in case (Section 5.4). MUST NOT write duplicate member names inmanifest.json(Section 6.4). - MUST NOT change the capitalization of file names taken from the source content (for example by lowercasing them), unless the user asks. Links are matched case-sensitively (Section 5.4.1), so renaming a file breaks the links to it.
- SHOULD include all assets referenced by the document's Markdown content.
- SHOULD include
producer.application.nameandproducer.application.version. - When a reusable core library/runtime is part of the producer pipeline, SHOULD include
producer.core.nameandproducer.core.version. - SHOULD include
producer.application.url,producer.core.url,author.url, andauthor.emailwhen available and appropriate to disclose.
A producer that modifies an existing archive (for example, adding an AGENTS.md or replacing a document) rather than creating one:
- MUST NOT change which file the archive's entry point resolves to, or leave it unresolvable, unless changing the entry point is the purpose of the modification.
- SHOULD add a
manifest.jsonwithspec.versionandentryPointwhen the archive does not have one, using the entry point the archive resolved to before the modification. - MUST preserve unrecognized manifest fields when rewriting
manifest.json, as defined in Section 6.3. - MUST preserve every archive entry it does not intend to change — including files it does not recognize, cannot render, or does not use, such as
AGENTS.md, nested support files, unknown file types and files in unfamiliar directories — keeping each entry's path and content byte-for-byte, unless the user explicitly asks for it to be removed. Compression method and ZIP metadata such as timestamps may change. - When it cannot display some content of an archive it lets the user edit (an unknown file type, an unsupported Markdown construct), SHOULD keep that content and make its presence visible to the user rather than dropping it on save.
Saving an existing archive that has no manifest without adding one is therefore conforming, though not recommended (item 2 above); a creation always writes one.
Conformance is a property of the archive a producer writes, not of the operation alone. A modification MUST NOT introduce a violation of Section 4, Section 5.4 or Section 6. An archive that already violates them (for example, with an entry compressed by an unsupported method, or an invalid name opened through an explicit recovery, Section 13.1) stays non-conforming when saved unless the violation is repaired. A producer that saves such an archive MUST either repair it with the user's agreement — for example by recompressing the entry with DEFLATE — or tell the user that the saved archive is still non-conforming. It MUST NOT repair it silently by dropping or renaming entries (Section 13.1).
Archives created by producers targeting earlier versions of this specification, which might not include a manifest, remain valid; consumers handle them as described in Section 14.2.
14.2 Conforming Consumer
A conforming consumer is any software that reads and/or processes .mdz files. A conforming consumer:
- MUST be able to read the entries of a valid ZIP archive, including entries stored with method 0 or 8 (Section 4). Extracting them to a filesystem is not required; a consumer may read entries directly from the archive.
- MUST determine the primary Markdown file using the entry point discovery algorithm defined in Section 5.5. When the manifest declares an
entryPointthat is invalid or not in the archive, MUST NOT silently fall back to another file; SHOULD reportERR_ENTRYPOINT_INVALIDorERR_ENTRYPOINT_MISSINGrespectively, and MAY recover using rules 2–4 of Section 5.5 with a visible warning. - MUST NOT silently select a Markdown file arbitrarily when no unambiguous entry point can be determined. The consumer SHOULD present the user with a list of available Markdown files or report a clear error.
- MUST parse
manifest.jsonwhen present, validate it as described in Section 6.4, and ignore unrecognized fields. MUST NOT requiremanifest.json; an archive without one MUST be processed using entry point discovery (Section 5.5) and the documented defaults. - If
manifest.modeis present and recognized: when the consumer supports that mode, MUST interpret the archive according to it as defined in Section 5.7; when it does not (for example, a document-only consumer opening aprojectarchive), MUST follow Section 5.7.6 — fail withERR_MODE_UNSUPPORTED, or fall back todocumentmode with a user-visible warning. - If
manifest.modeis present and unrecognized, MUST NOT silently fall back todocumentmode. SHOULD fail withERR_MODE_UNSUPPORTED. - If
manifest.modeis absent, MUST treat the archive asmode: "document". - If manifest
coveris invalid or references a missing file, SHOULD ignorecoverand continue processing the archive; MAY emit a warning. - MUST resolve asset and document links relative to the referencing file's location within the archive, as described in Section 9.3 (including percent-decoding), matching paths as described in Section 5.4.1 (NFC-normalized, case-sensitive).
- MUST handle entries that violate Section 5.4, including paths that would escape the archive root, as described in Section 13.1: never extract them under their stored names, never rename them silently, and never choose between duplicate or conflicting entries.
- MUST treat links outside the archive and links into archives as unresolved unless it supports them; when it does, MUST follow Sections 9.4–9.6, including treating them as unresolved when the archive has no host filesystem location. MUST NOT resolve such a link to a different target, such as a file inside the archive.
- MUST accept text files that use LF (
\n) or CRLF (\r\n) line endings. - If
spec.versionis present, MUST reject files where the manifestspec.versionmajor version exceeds the consumer's supported major version. - MUST accept both forms of
createdandmodified(object with requiredwhen, or a legacy RFC 3339 datetime string), whatever the archive'sspec.version(Section 6.1).
14.3 Conformance Fixtures
This repository publishes machine-readable conformance fixtures in examples/fixtures/, and the tests that use them in tests/README.md. Implementations SHOULD run them, pinned to a released tag of this specification.
The fixtures are non-normative. They illustrate the requirements in this document and do not add to them. When a fixture and the prose disagree, the prose governs and the fixture is a defect to report. Passing every fixture does not by itself make an implementation conforming.
15. Examples
15.1 Minimal .mdz Archive
A valid minimal .mdz archive contains a single Markdown file at the archive root. Naming it index.md is the recommended convention. This is the smallest archive a consumer must accept, for example one zipped by hand; an archive written by a conforming producer also includes manifest.json (Section 15.2):
Archive contents:
hello.mdz
└── index.md
index.md:
# Hello World
This is a minimal MDZip document.
15.2 Document with Assets
Archive contents:
my-guide.mdz
├── manifest.json
├── index.md
├── chapter-01.md
└── images/
├── cover.png
└── screenshot.png
manifest.json:
{
"spec": { "name": "mdzip-spec", "version": "1.2.0" },
"producer": {
"application": {
"name": "mdzip-cli",
"version": "1.2.0",
"url": "https://example.com/mdzip-cli"
},
"core": {
"name": "mdzip-core",
"version": "0.9.4",
"url": "https://example.com/mdzip-core"
}
},
"author": {
"name": "Alex Johnson",
"email": "alex@example.com",
"url": "https://example.com/~alex"
},
"title": "My Guide",
"mode": "document",
"entryPoint": "index.md",
"language": "en",
"cover": "images/cover.png"
}
index.md:
# My Guide
Welcome to the guide. See [Chapter 1](chapter-01.md) to get started.
chapter-01.md:
# Chapter 1: Getting Started
Here is a screenshot of the interface:

16. Implementation Guidance (Non-Normative)
This section is non-normative and is intended to help tool authors and AI-assisted coding agents implement this specification consistently.
16.1 Producer Build Checklist
When creating a .mdz archive, a producer can follow this sequence:
- Collect source Markdown and assets in a staging tree.
- Normalize text encoding to UTF-8 for Markdown and JSON files.
- Normalize line endings to LF (
\n) in Markdown files andmanifest.json; leave other files as they are (Section 12.2). - Validate file paths against Section 5.4.
- Write
manifest.json:- include required fields
spec.versionandentryPoint, plustitleandmode(recommended); - verify
entryPointreferences a Markdown file in the archive; - preserve unknown fields when rewriting an existing manifest (Section 6.3), including inside recognized objects.
- include required fields
- When modifying an existing archive, confirm the entry point still resolves to the same file afterwards (Section 14.1).
- List any links that point outside the archive (Section 9.4) and report them to the author as an informational notice, since the archive won't be self-contained. Don't rewrite them.
- Create an unencrypted ZIP archive and use
.mdzextension.
16.2 Consumer Load Checklist
When opening a .mdz archive, a consumer can follow this sequence:
- Open archive as ZIP using the central directory; reject encrypted entries and, optionally, compression methods other than 0 and 8.
- Enumerate entries and validate them against Section 5.4: no leading
/, no empty,.or..segments, no reserved or control characters, no NFC duplicates, and no file that is also a directory. Reject rather than rename (Section 13.1). - Parse and validate
manifest.jsonif present (Section 6.4): unreadable manifests and invalid critical fields are errors, invalid descriptive fields are ignored, unknown fields are kept. Do not reject the archive if it has no manifest. - Check
manifest.mode: apply a supported mode per Section 5.7; default todocumentif absent; for a recognized but unsupported mode, fail or fall back with a visible warning; fail withERR_MODE_UNSUPPORTEDif unrecognized. - Accept
createdandmodifiedin either form (object with requiredwhen, or legacy RFC 3339 datetime string). - If manifest
coveris invalid or references a missing file, ignorecoverand optionally warn. - Resolve primary Markdown file via Section 5.5:
- manifest
entryPointis not a valid entry point path: stop withERR_ENTRYPOINT_INVALID, or warn visibly before recovering with rules 2–4; - manifest
entryPointmatches an entry (NFC-normalized, case-sensitive): use it; - manifest
entryPointmatches no entry: stop withERR_ENTRYPOINT_MISSING(optionally naming a case-only near match), or warn visibly before recovering with rules 2–4; - no
entryPoint: apply rules 2–4, setting aside support files (README,AGENTS,LICENSE,CHANGELOG,CONTRIBUTING) when counting root-level Markdown files.
- manifest
- Parse Markdown with UTF-8 decoding and accept LF/CRLF text files.
- Resolve relative links per Section 9.3: links that stay inside the archive are internal; links that leave it (Section 9.4) or enter another archive (Section 9.5) are followed only if supported, a host location is known and the target is inside the trust boundary (Section 9.6), and are otherwise unresolved. Resources loaded automatically from outside the archive stay unresolved unless enabled for the trust boundary. Never clamp
..at the archive root. - If no unambiguous entry point exists, show explicit error or user choice (do not auto-pick arbitrarily).
16.3 Terminology Alignment (Producer vs. Create)
Normative specification text uses role terms (producer, consumer). Implementation APIs and commands may use action-oriented names such as create and add.
Recommended mapping:
- a
createAPI/command is a producer creation (Section 14.1); - an
add,updateor save API/command on an existing archive is a producer modification, which must preserve what it does not change.
16.4 Archive Update Semantics (Non-Normative)
When a producer supports adding files to an existing .mdz archive:
- path uniqueness should be preserved in the final emitted archive (avoid duplicate logical paths);
- adding a file to an existing path should replace that path's current entry in the resulting archive, rather than retaining duplicate ZIP entries for the same path;
- after successful updates, producers should refresh timestamp metadata (
modified, andcreatedonly when creating a new archive); - when rewriting
manifest.json, producers preserve unknown fields (Section 6.3), and they preserve archive entries they do not change byte-for-byte (Section 14.1). Test both across repeated open/save cycles: a single save that preserves data can hide a second save that drops it.
16.5 Suggested Error Categories
Implementations may expose stable error categories to simplify debugging and cross-tool behavior comparisons:
ERR_ZIP_INVALID: archive is not a valid ZIP container, or uses a compression method other than 0 or 8.ERR_ZIP_ENCRYPTED: archive or entries use unsupported encryption.ERR_PATH_INVALID: one or more entry paths violate Section 5.4.ERR_MANIFEST_INVALID:manifest.jsonis not UTF-8 JSON, its top-level value is not an object, or a critical field has the wrong type or an invalid value (Section 6.4).ERR_ENTRYPOINT_UNRESOLVED: no unambiguous primary Markdown file can be determined.ERR_ENTRYPOINT_INVALID:entryPointis not a valid entry point path — not a non-empty string, an unsafe or non-relative path, a directory, or not a.md/.markdownfile (Section 5.5).ERR_ENTRYPOINT_MISSING:entryPointis a valid path but matches no file in the archive, including a match that differs only in case.ERR_VERSION_UNSUPPORTED: manifestspec.versionmajor version is not supported.ERR_PATH_CONFLICT: a file's path is also the directory of another entry, such asdocsanddocs/intro.md(Section 5.4).ERR_DUPLICATE_PATH: duplicate logical archive paths were detected — two entries equal after NFC normalization, or duplicates left by an update operation.ERR_MODE_UNSUPPORTED: the manifest declares amodevalue that the consumer does not support or does not recognize.
16.6 AI Agent Prompting Tips
To improve deterministic implementation quality, AI prompts should explicitly include:
- the exact target spec version (
1.2.0or later); - whether producer, consumer, or both are being implemented;
- required conformance scope (minimum MUSTs vs. SHOULDs included);
- required behavior for ambiguous entry points;
- explicit test expectations from
tests/README.md.
17. Future Extensions (Non-Normative)
This section records possible future directions and does not define current conformance requirements for 1.2.0.
17.1 Manifest-Level Document Collections
Note: The
documentandprojectmode semantics introduced in Section 5.7 address the foundational use case described here. Thedocumentsarray for per-file metadata (title, order, authors) remains a potential future extension and is not defined in this version.
A future version may define an optional manifest field (for example, documents) to describe multi-file documents with per-file metadata.
Potential use cases:
- explicit reading/navigation order,
- per-file title/author metadata,
- chapter-level metadata for richer consumers.
One possible shape:
{
"documents": [
{
"path": "chapter-01.md",
"title": "Introduction",
"order": 1,
"authors": ["Jane Smith"]
}
]
}
If introduced, future revisions should define:
- interaction with
entryPoint, - behavior when listed files are missing or duplicated,
- ordering semantics and tie-breaking rules,
- backward compatibility for consumers that ignore unrecognized fields.
17.2 Reader Resume State (Last Read Position)
A future ecosystem convention may define how consumers remember a reader's last opened file and position for an .mdz document.
For interoperability and conflict avoidance, this state is best treated as consumer-local user state, not archive content. Implementations should prefer:
- local storage keyed by document identity (for example, path plus content hash),
- per-user state separation,
- non-destructive behavior for read-only archives.
Embedding "last read" data directly inside .mdz is not part of this specification and should be avoided unless a future version defines explicit multi-writer and conflict-resolution semantics.
17.3 Bookmarks
A future ecosystem convention may define bookmark support so readers can save and return to notable locations within MDZip documents.
As with resume state, bookmark data is best treated as consumer-local user state by default. Implementations should prefer:
- local per-user bookmark storage keyed by document identity,
- bookmark targets that can survive minor content edits where possible,
- non-destructive behavior for read-only archives.
Embedding bookmark data directly inside .mdz is not part of this specification and should be avoided unless a future version defines portable bookmark anchoring and conflict-resolution semantics.
17.4 Extension Design Principles
Future extensions should:
- preserve portability and minimal baseline interoperability,
- remain optional unless a clear interoperability need requires otherwise,
- avoid coupling the format to any single editor or rendering engine,
- include conformance tests and examples before becoming normative.
17.5 Proposal Process
Contributors proposing extensions should open an issue and include:
- problem statement and user impact,
- normative wording draft,
- compatibility analysis,
- proposed conformance tests.
See CONTRIBUTING.md for repository workflow.
End of Specification