The file
A ScriptPub work is one self-contained HTML file: .spub.html. The
double extension is the marker — the inner name says SPub, the outer says
HTML — and it is the only marker: detection is by extension and content,
never by a sidecar. There is no other canonical serialization; libraries,
multi-file assembly, and foreign formats are application concerns.
The recommended file name is Title-by-Author.spub.html.
Anatomy
<!doctype html>
<html lang="…"> ← derived from meta.language
<head>
<meta charset="utf-8" />
<title>…</title> ← derived from meta.title
… canonical/icon links, derived …
<script type="application/ld+json" id="scriptpub-meta"> …the metadata island… </script>
<script type="application/json" id="scriptpub"> …minimal config… </script>
<style id="scriptpub-style"> @layer spub-base { … } …document rules… </style>
</head>
<body>
…sections as semantic elements; blocks as elements…
</body>
</html>
Four parts, each specified on its own page:
- The metadata island —
<script type="application/ld+json" id="scriptpub-meta">, the one machine-readable home of every work fact: a prepared schema.org vocabulary plus thespub:context.<title>,<html lang>, and the canonical/icon links are derived spellings written from it (and the parse fallback when a file has no island). See Metadata. - The config island —
<script type="application/json" id="scriptpub">(below). - The stylesheet —
<style id="scriptpub-style">: the machine@layer spub-base { … }prelude, then the document’s own unlayered rules. See The stylesheet. - The body — sections as direct semantic children
(
<header>,<nav>,<main>,<footer>,<section class="…">), blocks as elements recognized by selector matching, bare text and unclassed<p>as the default Paragraph kind. A<main>-only body is unsectioned; body-level content outside any section element is edge blocks.
The config island
The island holds only what HTML and CSS cannot express — exactly these
keys of the FormatConfig:
scriptpub · blocks · marks · syntax · revisions
Never meta (the metadata island is its home), never sections (the DOM is),
never styling (the sheet is). The island is omitted entirely when the config is
trivial — nothing declared beyond the built-in vocabulary, no syntax settings, no
revisions. A config-less file is still a valid work: it parses under the
blank one-default-block config, which is how any plain HTML parses.
A complete minimal example
A short poetry work, whole and valid — every part of the format in one file:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Harbor Light</title>
<script type="application/ld+json" id="scriptpub-meta">
{
"@context": ["https://schema.org", { "spub": "https://scriptpub.org/ns#" }],
"@type": "Book",
"name": "Harbor Light",
"author": "A. Poet",
"inLanguage": "en",
"spub:preset": "scriptpub:curated/poetry"
}
</script>
<script type="application/json" id="scriptpub">
{
"scriptpub": 6,
"blocks": [
{ "id": "heading", "label": "Title", "selector": "h1",
"behavior": { "outline": true, "rank": 1 },
"editor": { "shortcut": "Mod-1", "enter": "line" } },
{ "id": "line", "label": "Line", "default": true,
"editor": { "shortcut": "Mod-2", "enter": "line" } },
{ "id": "stanza_break", "label": "Stanza break", "shape": "void", "selector": "hr" }
],
"marks": [
{ "id": "em", "label": "Italic", "selector": "em", "editor": { "shortcut": "Mod-i" } },
{ "id": "strong", "label": "Bold", "selector": "strong", "editor": { "shortcut": "Mod-b" } }
]
}
</script>
<style id="scriptpub-style">
@layer spub-base { /* machine prelude — regenerated on save, skipped on parse */ }
main { max-width: 40ch; font-family: var(--font-serif); }
h1 { font-size: 1.4rem; font-weight: bold; margin-top: 2.4rem; break-before: page; }
p { margin: 0 0 0 4ch; }
hr { border: none; margin-top: 1.2rem; }
</style>
</head>
<body>
<main>
<h1>Harbor Light</h1>
<p>The tide keeps what the pier lets go,</p>
<hr />
<p>And every light that says <em>come home</em> says <strong>stay away</strong> the same.</p>
</main>
</body>
</html>
Read the example against the rules:
- The metadata island carries the title, the author, and the preset
reference — the machine-readable metadata lives nowhere else. The
<title>and<html lang>restate two of its facts for browsers; they are derived spellings, regenerated on save. (Presets are app-managed; the file records only thespub:preseturi.) - The selectors do triple duty.
"h1"recognizes the heading kind, writes it as<h1>, and is the stylesheet rule that styles it — same forhr,em,strong. Thelinekind is the default: it declares no selector, writes as an unclassed<p>, and the unclassed<p>elements fall through to it on re-parse (bare text parses to the default kind too, though the serializer always writes the<p>). - The break is CSS. The heading starts a new page via
break-before: pagein the sheet — there is no behavior flag for it. - The body is one
<main>, so the document is unsectioned —<main>is just the content root. Adding a<header>title page would promote<main>to thebodysection. - Strip the island and the sheet and the file is still a proper, accessible web page — that is the point.