Manifest ReferenceManifest Structure

Manifest Structure

Anatomy of a PanelWave manifest — top-level sections, a minimal valid example, shared primitive types, and how IDs connect the pieces.

A PanelWave manifest is one JSON object. This page walks through its anatomy, shows the smallest valid manifest, defines the shared primitive types used throughout the schema, and explains how the sections reference each other.

Top-level sections

panelwave, meta, and chapters are required; everything else is optional. localization (schema 1.5+) holds the authoring tool's translation workflow state — locale list plus per-string entries with stale and machine-translation flags — so it survives transfers between authoring systems; rendering consumers may ignore it (reader-facing text stays in LocalizedString objects throughout the manifest). The root object rejects unknown properties except x- prefixed extension fields. See the Overview for the full top-level table.

A minimal valid manifest

The smallest useful manifest has a header, metadata, one asset, and one chapter with two panels connected by one edge (graph.edges is required but may be empty since 1.7):

{
  "panelwave": {
    "version": "1.3.0",
    "schema": "https://panelwave.org/schema/1.0/panelwave.schema.json"
  },
  "meta": {
    "id": "work-minimal",
    "title": { "en-US": "Minimal Example" },
    "locales": ["en-US"],
    "default_locale": "en-US"
  },
  "assets": {
    "catalog": [
      {
        "id": "img-p1",
        "category": "image",
        "alt": { "en-US": "Opening panel" },
        "variants": [
          { "src": "https://cdn.example.com/p1.jpg", "w": 1280, "h": 720, "mime": "image/jpeg" }
        ]
      },
      {
        "id": "img-p2",
        "category": "image",
        "alt": { "en-US": "Second panel" },
        "variants": [
          { "src": "https://cdn.example.com/p2.jpg", "w": 1280, "h": 720, "mime": "image/jpeg" }
        ]
      }
    ]
  },
  "chapters": [
    {
      "id": "ch-1",
      "title": { "en-US": "Chapter One" },
      "panels": {
        "p1": {
          "layers": [
            { "kind": "image", "id": "ly-p1-bg", "assetId": "img-p1", "z": 0 }
          ]
        },
        "p2": {
          "layers": [
            { "kind": "image", "id": "ly-p2-bg", "assetId": "img-p2", "z": 0 }
          ]
        }
      },
      "graph": {
        "entry": "p1",
        "edges": [
          { "from": "p1", "to": "p2", "transition": { "type": "slide", "dir": "left", "durationMs": 300 } }
        ]
      }
    }
  ]
}

Reading it top to bottom:

  1. panelwave pins the format version and schema URI — see Versioning.
  2. meta identifies the work and declares its locales. title is a LocalizedString, not a plain string — see Meta.
  3. assets.catalog registers every media file once, keyed by id, each with one or more variants — see Assets.
  4. chapters[].panels is a map of panel ID → Panel. Panel IDs are the map keys (validated as Identifiers), and layers point at catalog entries via assetId.
  5. chapters[].graph defines reading flow: an entry panel and directed edges between panel IDs — see Graph.

Larger manifests add pages (print/page layouts), variables (branching state), settings, extras, paywall, tracking, and ui. Each has its own reference page.

Shared primitives

These $defs are reused across the entire schema. Other reference pages link back here instead of repeating them.

Identifier

Every ID in the manifest (works, chapters, panels, assets, characters, layers, …).

  • Type: string, pattern ^[a-zA-Z0-9][a-zA-Z0-9._:-]*$, length 1–200.
  • Starts with a letter or digit; may contain ., _, :, -.
  • IDs are strings, never array indices — references stay stable when arrays are reordered.
"img-cover"        ✅
"ch-1"             ✅
"panel:alt.v2"     ✅
"-leading-dash"    ❌ (must start alphanumeric)
"has spaces"       ❌

LocaleCode

BCP-47-like locale tag: pattern ^[A-Za-z]{2,8}(-[A-Za-z0-9]{2,8})*$. Examples: en-US, de-DE, ja, pt-BR.

LocalizedString

An object mapping locale codes to strings; at least one entry required, no non-locale keys allowed. Used for all user-facing text.

{
  "en-US": "Night Shift",
  "de-DE": "Nachtschicht"
}

Consumers resolve the reader's locale against meta.locales with fallback to meta.default_locale — see Localization.

Uri, ColorHex, Timestamp

TypeDefinitionExample
Uristring with format: "uri" (absolute URI)https://cdn.example.com/a.jpg
ColorHex^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$#0055ff, #f00
TimestampISO 8601 date-time string2026-07-01T12:00:00Z

NormalizedNumber, NormalizedPoint, NormalizedRect

Geometry is resolution-independent — coordinates are fractions of the containing box:

  • NormalizedNumber — number in [0, 1].
  • NormalizedPoint — [x, y] tuple of two normalized numbers.
  • NormalizedRect — { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 }, all four required, all normalized. x/y is the top-left corner (0 = left/top edge, 1 = right/bottom edge).

Used for focus rects, clip rects, viewport animations, hotspot shapes, and bubble bounding boxes.

JsonValue and JsonLogic

  • JsonValue — any JSON value (null, boolean, number, string, array, object). Used for variable defaults, mutation values, and plugin props.
  • JsonLogic — a JSON Logic expression. The schema accepts any JSON shape here (it is not strictly validated); evaluation happens at runtime against the variable state. Used by edge condition, layer/bubble/hotspot visibleIf, and panel variant when.
{ "and": [ { ">=": [ { "var": "user.age" }, 16 ] }, { "==": [ { "var": "path.choice" }, "left" ] } ] }

Transition

Animation between panels, used on edges, hotspot actions, page in/out, and format preset defaults:

PropertyTypeConstraintsDescription
typestringnone, cut, fade, slide, zoom, push, coverTransition kind
dirstringleft, right, up, downDirection (for directional types)
durationMsinteger0–60000Duration in milliseconds
easingstringlinear, ease, ease-in, ease-out, ease-in-outTiming function

OutputFormat

Enum of target formats: flex-landscape, mobile-portrait, bigscreen-landscape, a4-portrait, a4-landscape, us-portrait, us-landscape, video-16-9, square, desktop-landscape, tablet-portrait. Used by page layouts, panel formatViews, and settings output presets.

How the pieces reference each other

All cross-references are by Identifier string:

ReferencePoints to
Layer / audio track / video layer assetIdassets.catalog[].id
meta.coverCatalog asset ID (preferred) or an absolute URI for static bundles
Speech bubble characterIdmeta.characters[].id
graph.entry, edge from / to, hotspot goTo.toPanel keys in the same chapter's panels map
pages[].layout.placements[].panelId, pages[].readingOrder[]Panel keys in the same chapter's panels map
Panel contentWarnings[]meta.content_warnings[].id
Panel preloadHints[]Asset catalog IDs to prefetch
paywall.rules[].refIdChapter, panel, or extras ID depending on rule scope
extras.character_sheets[].characterId / characterIds[]meta.characters[].id
panels.<id>.animations.keyframes[].layerIdlayers[].id of the same panel
Mutation / condition varvariables.definitions[].id

JSON Schema validation does not verify that these references resolve — a manifest with a dangling assetId is schema-valid but broken at runtime. Run reference checks in your pipeline; see Validation.

There is also a general-purpose AssetRef definition (plain ID, URI, or { "assetId", "variant", "locale" } object) for pinning a specific variant or locale of an asset — see Layers for details.