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:

  1. 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 the spub: 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.
  2. The config island<script type="application/json" id="scriptpub"> (below).
  3. The stylesheet<style id="scriptpub-style">: the machine @layer spub-base { … } prelude, then the document’s own unlayered rules. See The stylesheet.
  4. The bodysections 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 the spub:preset uri.)
  • The selectors do triple duty. "h1" recognizes the heading kind, writes it as <h1>, and is the stylesheet rule that styles it — same for hr, em, strong. The line kind 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: page in 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 the body section.
  • Strip the island and the sheet and the file is still a proper, accessible web page — that is the point.