Add MDZip to Your App
This page is for developers of Markdown editors, note apps and converters who want their app to open or save .mdz files. Support comes in steps, and each one is useful on its own. MDZip's libraries handle the format itself (finding the main document, matching paths, reading and writing the manifest, rejecting unsafe archives), so most of the work is connecting them to your app.
If you use an AI coding assistant, skip to prompts for your AI assistant.
Choose how far to go
| Step | What your users get | Work |
|---|---|---|
| 1. Open | Read an .mdz someone sent them, pictures included |
Small |
| 2. Export | Share a note and its images as one file | Small |
| 3. Drop-in viewer | A full preview with navigation and multi-document archives (browser and webview apps) | Moderate |
| 4. Edit in place | Open an .mdz, change it and save it back |
Larger: your app decides how saving works |
Steps 1 and 2 together cover sharing. Step 4 is what lets people keep their documents as .mdz in your app.
Choose a library
| Platform | Library | Install |
|---|---|---|
| JavaScript and TypeScript: browser, Electron, Tauri, Node.js | @mdzip/core-js |
npm install @mdzip/core-js |
| Browser and webview UI | @mdzip/editor, a ready-made viewer and editor (with React, Vue and Angular wrappers) |
npm install @mdzip/editor |
| .NET 8 and later | MDZip.Core |
dotnet add package MDZip.Core |
| Scripts and anything else | The mdz CLI |
winget install MDZip.Cli, or see Install |
All of them are open source under the Apache 2.0 license, and the core libraries run the specification's shared test files. A Python library is in development.
Step 1: Open and show
Open the archive, find its main document and render it with the Markdown renderer your app already uses. Then point each image at its copy inside the archive:
import { MdzArchiveCore } from '@mdzip/core-js';
const archive = await MdzArchiveCore.open(bytes); // Uint8Array, ArrayBuffer or Blob
const entry = await archive.resolveEntryPoint(); // the main document, e.g. "index.md"
const markdown = await archive.readText(entry);
// Render with your own Markdown renderer and sanitizer, into a <template>
// so the browser doesn't try to fetch the images from your own server.
const template = document.createElement('template');
template.innerHTML = sanitize(renderMarkdown(markdown));
// Point each image at its copy inside the archive, then show the result.
for (const img of template.content.querySelectorAll('img')) {
const target = archive.resolveLink(entry, img.getAttribute('src') ?? '', undefined, { use: 'embed' });
if (target.kind === 'internal') img.src = await archive.readDataUri(target.entry);
else if (target.kind !== 'url') img.removeAttribute('src'); // missing: don't look elsewhere
}
preview.replaceChildren(template.content);
Image paths are relative to the document that uses them, so resolveLink takes the document's path too. It also turns away paths that would leave the archive. Images from the web (https:) are left as they are.
In .NET:
using MDZip.Core;
var entry = MdzArchive.ResolveEntryPointStrict("notes.mdz"); // the main document
var markdown = MdzArchive.ReadText("notes.mdz", entry);
// For each image in the rendered document:
var target = MdzArchive.ResolveLink("notes.mdz", entry, src, use: "embed");
if (target.Kind == "internal")
src = MdzArchive.ReadDataUri("notes.mdz", target.Entry!);
Errors such as an archive with no main document come back with a code at the start of the message (ERR_ENTRYPOINT_MISSING, for example), so show the message to the user.
Step 2: Export
Pack the document and the images it uses into a new archive. Rewrite the image references in the Markdown so they point at the paths inside the archive:
import { MdzPackagerCore } from '@mdzip/core-js';
const files = [
{ path: 'index.md', text: noteMarkdown }, // image links point at images/...
{ path: 'images/beach.png', data: imageBytes }, // every image the note uses
];
const { blob } = await MdzPackagerCore.buildArchive(files, 'Trip notes', {
title: 'Trip notes',
entryPoint: 'index.md',
createIndex: false,
mapFiles: false,
filters: ['**/*'],
});
const bytes = new Uint8Array(await blob.arrayBuffer()); // write these bytes to disk
filters decides which files go in, so it must be ['**/*']: an empty list leaves every file out. The library writes the manifest.json for you.
In .NET:
MdzArchive.CreateFromFiles("trip.mdz",
new[] { ("index.md", "note/index.md"), ("images/beach.png", "note/images/beach.png") },
new Manifest { Title = "Trip notes", EntryPoint = "index.md" }); // using MDZip.Core.Models
Step 3: Use the drop-in viewer
For an app built on web technology, @mdzip/editor gives you the same viewer and editor as MDZip Studio and the VS Code extension: rendering, images, navigation between documents, and Mermaid diagrams.
import { MdzipWorkspaceView } from '@mdzip/editor';
const view = new MdzipWorkspaceView(container, {
controls: 'viewer', // or 'hosted-editor' to edit
onFailed: (error) => console.error(error),
});
await view.open(bytes, { mode: 'read-only', fileName: 'notes.mdz' });
The Editor Developer Guide covers editing, saving, theming and the framework wrappers.
Step 4: Save changes back
Write only what changed. The library keeps everything else in the archive as it was, including images, files your app doesn't know about and manifest fields written by other apps:
const { blob } = await MdzArchiveCore.updateFiles(original, [
{ path: entry, content: editedMarkdown },
]);
In .NET, MdzArchive.UpdateFiles("notes.mdz", new[] { new ArchiveWriteSpec(entry, "edited.md") }) does the same from a local file.
Don't extract the archive to a folder and zip it up again with a general ZIP tool. That loses anything your app didn't understand, and the result may not be a valid .mdz.
Rules your app is responsible for
The libraries handle most of the specification. These parts are up to your app:
- Never run anything from an archive. Sanitize the rendered HTML, as you would for any Markdown from someone else: no scripts, no event handlers, no
javascript:links (§13.2). - Load images from inside the archive only. Don't fetch an archive's images from the web or from files next to it unless the user turns that on (§9.6). Resolve them before the HTML reaches the page, as in step 1.
- Treat paths as case-sensitive.
Images/Logo.pngandimages/logo.pngare different files. The libraries match paths this way; keep your own lookups the same. - Open
.mdzfiles by extension, and use the media typeapplication/vnd.mdzipwhere your platform needs one (§10).
Test your support
- Example archives: a minimal file, one with images and a multi-document project.
- Conformance tests: the specification's test cases and fixture archives, including tricky entry points, unusual paths and a hostile-content archive that checks your preview runs no scripts.
mdz validate: check the files your app writes. Aim for no warnings as well as no errors.
Prompts for your AI assistant
Paste one of these into your AI coding assistant, in your app's project. Each one names the library calls to use and the mistakes to avoid. We tested them on 2026-10-21 with two small sample apps that had never seen MDZip, a browser Markdown editor and a .NET converter. Each one worked on the first try, and we checked the results with mdz validate and a browser.
Open .mdz files (JavaScript or TypeScript)
Add read-only support for opening MDZip (.mdz) files to this app.
An .mdz is a ZIP archive holding one or more Markdown documents, the images they use and a manifest.json. Spec: https://mdzip.org/spec.html. Developer guide: https://mdzip.org/docs/integrate.html
Use the npm package @mdzip/core-js (install that exact name; some older docs say "mdzip-core-js", which is wrong). Read node_modules/@mdzip/core-js/README.md and AGENTS.md, and the type definitions in its dist/ folder, before writing code.
- Accept .mdz wherever the app accepts .md files today.
- Open it with MdzArchiveCore.open(bytes) and find the main document with archive.resolveEntryPoint(). Don't guess it from file names.
- Render the main document with the app's existing Markdown renderer and sanitizer.
- Show its images from inside the archive. Do this before the HTML is added to the page (for example, in a <template>), so the browser never requests them from anywhere else. For each <img> (Markdown and raw HTML images alike), pass its src attribute as rendered to archive.resolveLink(entry, src, undefined, { use: 'embed' }):
- kind 'internal': set src to await archive.readDataUri(result.entry);
- kind 'url' (https: and data: images): leave it as it is;
- anything else: the image isn't in the archive, so remove the src.
- If the preview re-renders while the user types, cache the data URLs instead of reading the archive again each time.
- Don't extract the archive to disk or a temp folder.
- Keep it read-only for now: don't write changes back into the .mdz. Tell me how saving behaves after opening one.
- If the file isn't a valid .mdz, show the error message (it starts with an ERR_ code) instead of failing silently.
When you're done, build the project and test it with https://mdzip.org/examples/with-images.mdz and with a sample .mdz whose main document is in a subfolder and uses an image in another folder. List the files you changed.
Export as .mdz (JavaScript or TypeScript)
Add "Export as .mdz" to this app, next to its other export options.
An .mdz (MDZip) is a ZIP archive holding a Markdown document, the images it uses and a manifest.json. Spec: https://mdzip.org/spec.html. Developer guide: https://mdzip.org/docs/integrate.html
Use the npm package @mdzip/core-js (install that exact name; some older docs say "mdzip-core-js", which is wrong). Read node_modules/@mdzip/core-js/README.md and AGENTS.md, and the type definitions in its dist/ folder, before writing code.
- Package the current document as index.md with MdzPackagerCore.buildArchive(files, title, { title, entryPoint: 'index.md', createIndex: false, mapFiles: false, filters: ['**/*'] }). Files are { path, text } for the Markdown and { path, data } (a Uint8Array) for images. filters must be ['**/*']: an empty list leaves every file out.
- Include every image the document uses that the app can read: images embedded as data: URLs, and local files if the app has access to them. Put them under images/ and rewrite the document's image references, Markdown and HTML <img> alike, to those relative paths, percent-encoding spaces, #, ? and %. Leave http: and https: images as they are.
- Give each image its own path, even when two have the same name. Use the same path when the same image is used twice.
- If an image can't be read, still export, and tell the user which images were left out.
- Use the document's first heading as the title, or else its file name.
- Don't change how the app saves .md files.
When you're done, build the project, export a document with at least two images, check the result with MdzArchiveCore.validate() (no errors or warnings), check that every rewritten image path is in the archive, and list the files you changed.
Open .mdz files (.NET)
Add support for MDZip (.mdz) files as input to this app.
An .mdz is a ZIP archive holding one or more Markdown documents, the images they use and a manifest.json. Spec: https://mdzip.org/spec.html. Developer guide: https://mdzip.org/docs/integrate.html
Use the NuGet package MDZip.Core (namespace MDZip.Core). Its README doesn't list every method; the ones below exist.
- Find the main document with MdzArchive.ResolveEntryPointStrict(path) and read it with MdzArchive.ReadText(path, entry). Don't guess it from file names.
- For each image the document uses, Markdown and raw HTML <img> alike, call MdzArchive.ResolveLink(path, entry, src, use: "embed") with the URL as the Markdown parser gives it:
- Kind "internal": use MdzArchive.ReadDataUri(path, target.Entry) as the image source;
- Kind "url" (https: and data: images): leave it as it is;
- anything else: the image isn't in the archive; leave it, and warn the user.
- Don't extract the archive to disk or a temp folder.
- Errors are InvalidOperationExceptions whose message starts with an ERR_ code (MdzSpec.ErrorCode(ex) reads it). Report them as one line, not a stack trace.
When you're done, build the project and test it with https://mdzip.org/examples/with-images.mdz and with a sample .mdz whose main document is in a subfolder and uses an image in another folder (set entryPoint in its manifest.json). List the files you changed.
Tell us about it
When your app supports .mdz, open an issue so we can list it on Tools. Questions about the format or the libraries are welcome there too.
This page renders from integrate.mdz (MDZip, ~6 KB, updated 2026-10-21) — one self-contained file. Download it, open it in the web editor, or read how these pages are made.