Spec
The deck format
The portable, versioned shape a presentation serializes to, so a second implementation can read, render and round-trip it without Deckyard's server or its storage.
A deck stores presentation data, with slides as an array of { type, content } records. The JSON describes the content independently of its rendering, but local upload references still depend on the source server.
The envelope
The envelope holds the format identifier and version, presentation metadata and slides. Optional lang and translations fields carry language information.
{
"format": "deckyard.deck",
"version": 1,
"title": "My deck",
"theme": "default",
"slides": [
{
"type": "eu.deckyard.slide.title",
"content": {
"title": "My deck",
"subheading": "An example in the open deck format",
"background": "lime"
}
},
{
"type": "eu.deckyard.slide.quote",
"content": {
"quote": "A deck is data, not a rendering.",
"authorName": "Deckyard",
"authorTitle": "Format specification"
}
}
]
} The envelope is lenient. Unknown top-level keys are ignored by an importer, never rejected, so a newer producer can add a field that an older reader simply skips.
The documentation lists every top-level field, including `lang` and `translations`, with its type and the reader’s responsibilities. Full reference
Slides, and the one spelling of a type id
A slide is a type and a content object whose shape that type defines. An absent or empty field means "unset": an importer fills the type's defaults and never blanks a required field. Portable slides carry no id, because ids are a storage concern and are regenerated on import.
slides[].type is the type's canonical id, and a type has exactly one: reverse-DNS for a declarant with a domain (eu.deckyard.slide.title), namespace/name for one without. The id names the definition the slide was written against and may pin a version (@2), so there is no separate manifest to cross-check. Two older spellings - the bare registry key title-slide and the qualified core/title-slide - are pre-convergence residue, not part of the format: Deckyard still accepts and normalizes them on import, but what it exports is canonical, and a second implementation owes them nothing.
{ "type": "eu.deckyard.slide.content", "content": { "title": "Why", "body": "..." } } Content schemas
Each slide type's content shape is described by a JSON Schema generated from the same field registry that drives validation and builds the editor form. One declaration, four consumers, and no way for a schema to describe a shape the software does not accept.
They are versioned by their $id, which carries the version of the content shape (15) rather than the envelope version (1). https://deckyard.eu/schema/v15/deck.schema.json is the whole deck, https://deckyard.eu/schema/v15/slide-types/<type>.schema.json is one type, and https://deckyard.eu/schema/v15/index.json hands you the list. All three resolve: a $id is not formally required to be fetchable, and a format offered to other people as a standard should be anyway.
Additional properties are allowed. The schemas describe the known shape of a slide. Published schema paths remain available, including schemas for types that may later be retired, so a saved deck can retain its schema reference.
Asset references
Images are referenced by string. A local upload is a server path, portable only while that server is reachable. An external https:// URL is already portable and is left untouched by every transform.
The package: a deck with its pixels
Where the JSON export still points at images on a server, the package carries its own. It renders and round-trips on a machine that has never seen the instance it came from, and it can enumerate exactly which assets it contains.
The layout is modelled on OCF, the container EPUB uses, for the same reason EPUB uses it: a ZIP whose first entry is an uncompressed media type is identifiable by magic number before anything unpacks it. Six entries, in this order; two of them only when the deck needs them.
mimetype
manifest.json
deck.json
theme.json
slide-types/<slug>.json
assets/<sha256>.<ext> -
mimetype - First entry, stored uncompressed, containing exactly
application/vnd.deckyard.deck. -
manifest.json - Package metadata and the complete asset inventory, with the hash of the theme and of every slide type the package carries. The original filenames stay in it, so hash churn never leaks into the readable structure, and several sources against one hash means the same bytes were referenced from several places.
-
deck.json - The portable envelope, with every asset reference rewritten to point inside the archive.
-
theme.json - Only when the deck is on a database theme: the theme record itself, without the id that means nothing on another instance. A file theme ships with an install and travels by its id in
deck.jsonalone. -
slide-types/<slug>.json - One file per database slide type the deck uses, built in the app rather than in code. Core types and types defined in code travel by the type id on the slide.
-
assets/<sha256>.<ext> - The asset bytes, addressed by the SHA-256 of their own content: slide images, theme logos and open-licence font files. Identical bytes are stored once.
That media type is registered. application/vnd.deckyard.deck was assigned in IANA's vendor tree on 13 August 2026, by Expert Review. A vendor-tree registration is not standardisation: it records the name, establishes that it is nobody else's, and points at this specification. The registration template
-
Self-contained
Every local slide image is embedded, and so are a database theme's logos and open-licence fonts. That package renders offline. Four things stay outside it: images linked by an external
https://URL, a file theme, assets referenced from inside CSS values, and fonts that travel by name only: licensed ones, and any the sending instance holds no file for. -
Content-addressed and verifiable
Each asset's bytes hash to its own reference. The reader re-hashes everything on the way in and rejects a mismatch, so a corrupted or tampered archive fails loudly.
-
Deduplicated
Identical bytes are stored once, however many slides point at them.
-
Enumerable
The manifest lists every asset a deck needs. You can answer "what is in here" without unpacking it.
Every manifest field, and what import does with each one, is written out in the documentation. Full reference
What is guaranteed, and what is lossy
For content-bearing slides, export to import to export is a fixpoint: after one normalization pass the portable projection is stable, and identical asset bytes hash to identical addresses. A test in the core repo proves this against a committed example deck on every run. Two edges are deliberately not lossless, and both are specified so that they degrade rather than crash - which is what makes the format safe to implement against.
- Unknown slide type
- Imports as a placeholder that names the type it could not resolve, says whether it was deliberately retired and what replaces it, and carries the original content across as text.
- Missing local asset
- Keeps its original reference and imports as a dangling one. Harmless, and visible rather than silent. The manifest lists such references under
missingAssets, and fonts that travel by name only underfontsNotIncluded, so a reader knows what it has to fetch or substitute.
Treat author content as untrusted input
The format has no macros or scripting language of its own. A custom-html-slide can contain author-supplied HTML, and an embed-slide can reference an external URL. Readers must treat both as untrusted input; passing schema validation does not make that content safe to display.
What follows is a duty on whoever reads the format, not a property of it: sanitise author HTML before it reaches a document, and isolate embedded URLs in a frame that cannot reach the page around it. The package layer does not change this. Content addressing proves the bytes are the bytes that were packed; it says nothing about whether they are safe to run.
Versioning
version identifies the envelope version and changes when the envelope shape breaks compatibility. Slide content has a separate schema version and migration runner. Readers validate against the version they support and tolerate unknown keys under the format’s leniency rules.