Deck package
A .deck package is a self-contained, portable archive of a presentation and
its assets. Where the JSON export (/export/json) carries only the deck and
still points at server-hosted /uploads/… images, the package carries its own
pixels — so it renders and round-trips on another machine without the server,
and it can enumerate exactly which assets it needs.
The layout is OCF/EPUB-inspired.
Archive layout
Section titled “Archive layout”mimetype First entry, STORED (uncompressed). Content: "application/vnd.deckyard.deck". Lets the archive be identified by magic number.manifest.json Package metadata + the asset inventory (see below).deck.json The portable deck (as from presentationToDeck), with every asset ref rewritten to a bundle ref.theme.json The deck's database theme, when it is on one (see Theme). Absent for a file theme.slide-types/<slug>.json Each database slide type the deck uses (see Custom slide types). Absent for core and file-JS types.assets/<sha256>.<ext> The asset bytes, content-addressed by SHA-256 of the content: slide images, theme logos and curated font files. Identical bytes are stored once (dedup).The media type is application/vnd.deckyard.deck.
It is registered with IANA, in the vendor tree, assigned 2026-08-13 after Expert
Review: https://www.iana.org/assignments/media-types/application/vnd.deckyard.deck.
That records the name and points at the specification; it is not standards-track,
and changing the registration goes through the same review. Packages written
before 1.7.0 carry application/vnd.slidecreator.deck, which a reader keeps
accepting for good; see
Legacy sentinel. The file
extension is unaffected either way.
manifest.json
Section titled “manifest.json”{ "format": "deckyard.deck", "bundleVersion": 3, "mimetype": "application/vnd.deckyard.deck", "deck": "deck.json", "theme": { "ref": "theme.json", "hash": "9c1f…07ab" }, "slideTypes": [ { "slug": "hero", "ref": "slide-types/hero.json", "hash": "41d0…9e2c" } ], "assets": [ { "ref": "assets/e2e9…445a.png", "id": "sha256-4unkYiBMX+HF…", "hash": "e2e9…445a", "mime": "image/png", "bytes": 1265204, "sources": ["/uploads/photo-1a2b.png"] }, { "ref": "assets/77d0…c3e1.woff2", "id": "sha256-d9DK…", "hash": "77d0…c3e1", "mime": "font/woff2", "bytes": 48212, "fontFaces": [ { "family": "Inter", "weight": 400, "subset": "latin" }, { "family": "Inter", "weight": 700, "subset": "latin" } ] } ], "missingAssets": ["/uploads/gone.png"], "fontsNotIncluded": [ { "family": "Brand Sans", "role": "body", "source": "adobe", "reason": "licensed" } ]}| Field | Type | Notes |
|---|---|---|
format | string | The same sentinel the deck carries. |
bundleVersion | integer | Package-layout version, 3 today. Independent of the envelope version. |
mimetype | string | Mirrors the mimetype entry. |
deck | string | Path of the deck inside the archive. Always deck.json. |
assets[].ref | string | Where the bytes live in the archive; also the value used inside deck.json. |
assets[].id | string | An SRI-shaped integrity id (sha256-<base64>), the stable, algorithm-tagged identity of the asset. |
assets[].hash | string | The hex SHA-256 (the content address; matches the ref name). |
assets[].mime | string | The media type of the bytes. |
assets[].bytes | integer | Size in bytes. |
assets[].sources | array | On a slide image or theme logo: the original /uploads/… name(s) that mapped to this asset. This is the separate name layer: human names stay in the manifest so hash churn never leaks into the readable structure. Multiple sources means the same bytes were referenced from several places. |
theme | object | Optional. { ref, hash } of theme.json when the deck is on a database theme. The reader re-hashes it like an asset and rejects a mismatch. |
slideTypes | array | Optional. One { slug, ref, hash } per database slide type the deck uses; ref is always slide-types/<slug>.json. The reader refuses another path, a hash mismatch, or a file that names another slug. |
assets[].fontFaces | array | On a font file instead of sources: every face (family, weight, subset) the file serves. |
missingAssets | array | Optional. Local refs whose bytes could not be read at export time; these keep their original ref in deck.json (or theme.json). |
fontsNotIncluded | array | Optional. Theme fonts that travel by name only, each with its role (heading/body), source and a reason: licensed (an upload, Adobe or Monotype font, licensed to the sending organization) or not-vendored (the sending instance holds no file for it). |
deck.json
Section titled “deck.json”The portable deck (presentationToDeck output: format, version, title,
theme and slides — see
Deck format). Asset refs in slide content are
rewritten from /uploads/x.png to the bundle ref assets/<hash>.<ext>. External
(http(s)://) image URLs are left untouched — they are already portable and are
not fetched into the package.
Guarantees
Section titled “Guarantees”- Self-contained: all local slide assets are embedded, and so are a
database theme’s logos and curated fonts. What is not: external
https://images, a file theme, fonts that travel by name only (fontsNotIncluded) and assets referenced from inside CSS values. A package whose deck uses any of those still needs the network, or the same install, to render as authored. - Content-addressed + verifiable: each asset’s bytes hash to its
ref/hash; the reader (readDeckBundle) re-hashes every asset and rejects a mismatch. - Deduplicated: identical bytes are stored once regardless of how many slides reference them.
- Enumerable: the manifest is a complete inventory of the deck’s assets.
Import (re-hydrating a package)
Section titled “Import (re-hydrating a package)”POST /api/presentations/import/deck takes a raw .deck body and creates a
presentation from it — the mirror of the export. The flow:
readDeckBundle(buffer)— verify the mimetype sentinel and re-hash every asset (integrity), yielding{ manifest, deck, assets }.- For each manifest asset, write its bytes back into
/uploads/viasaveUploadedFile, using the manifestsources[0]as the human basename. This builds anassets/<hash>.<ext>→/uploads/<uuid>.<ext>map. rewriteBundleRefs(deck, mapFn)— rewrite the deck’s bundle refs to the new upload URLs (the inverse of the export’srewriteAssetRefs).deckToPresentationParts+createPresentation/updatePresentation— the same normalization + creation path as the JSON import.
Round-trip: for content-bearing slides, export → import → export is a
fixpoint (identical content-addressed refs, since identical bytes hash the same).
Graceful degradation:
- An asset whose mime is unsupported by
saveUploadedFile(or that otherwise fails to write) is skipped — its ref is left in place and reported in afailedAssetsfield on the response, rather than crashing the import. - Unknown slide types become a
content-slideplaceholder that names the missing type, says whether it was deliberately removed and what replaces it, and carries the original content across as markdown. Import persists rather than renders, so it applies the same archived-slide contract as every render surface. - Local refs that were already missing at export time (
missingAssets) keep their original/uploads/…ref and import as dangling (harmless) references.
Theme and custom slide types
Section titled “Theme and custom slide types”A deck on a database theme carries that theme as theme.json: the fields a
theme is created from, with its logos and curated (open-licence) font files
going through the same asset walk as slide images. Licensed fonts travel by
name only, listed in fontsNotIncluded. A file theme ships with an install
and travels by the id in deck.json alone.
Each database slide type the deck uses travels as slide-types/<slug>.json,
likewise the fields it is created from. Core and file-JS slide types are code:
they travel by the type id on the slide, and an install that lacks them imports
the placeholder.
In the core repository:
- Build:
server/export/deck-bundle.js→buildDeckBundle(repoRoot, pres). - Read/validate:
readDeckBundle(buffer)→{ mimetype, manifest, deck, assets }. - Import:
server/routes/api/presentations/import-deck.js→handlePresentationsImportDeck(routePOST /api/presentations/import/deck). - Pure ref layer:
shared/slide-types/deck-assets.js(collectAssetRefs,rewriteAssetRefs,rewriteBundleRefs,assetRefForHash). - Export route:
GET /api/presentations/:id/export/deck.zip(downloads<title>.deck).