Manifest ReferenceExtensions & Plugins

Extensions & Plugins

Reference for x- extension fields, PluginInstance, PluginLayer, AssetCatalogItemPluginPayload, and the JsonValue type.

PanelWave is designed to be extended without breaking the format: x- extension fields carry tool-specific data, and the plugin system embeds interactive third-party content in panels.

x- extension fields

Custom properties whose names start with x- are reserved for extensions. The manifest root, Panel and ExtraBlock (the last two since format 1.7) each declare:

"patternProperties": { "^x-": {} }

so any x--prefixed property with any value is valid at the top level of a manifest — and, since 1.7, on a panel or an extras block (for example a server-side paywall view marks stripped panels "x-locked": true):

{
  "panelwave": { "version": "1.3.0", "schema": "https://panelwave.org/schema/1.0/panelwave.schema.json" },
  "meta": { "id": "demo", "title": { "en-US": "Demo" }, "locales": ["en-US"], "default_locale": "en-US" },
  "chapters": [ { "id": "ch-1", "panels": { "p1": {} }, "graph": { "entry": "p1", "edges": [{ "from": "p1", "to": "p1" }] } } ],
  "x-studio-editor-state": { "collapsed": false },
  "x-acme-pipeline": { "buildId": "2026-07-01.3" }
}

Strict-validation caveat: the ^x- pattern is declared on the manifest root, on Panel and on ExtraBlock (including ExtraCharacterSheet) — the latter two since 1.7. Other nested objects (Meta, Layer types, Hotspot, …) are closed with additionalProperties: false / unevaluatedProperties: false, so an x- field inside them fails strict AJV validation. If you need structured custom data elsewhere in the tree, use x- fields at those three places, PluginInstance.props or plugin payload assets rather than ad-hoc nested x- keys.

Naming convention: x- followed by a vendor/tool identifier, e.g. x-panelwave-cms-…, x-mytool-…. Consumers must ignore extension fields they do not understand.

Plugin system

Plugins embed custom interactive content (mini-games, 3D viewers, quizzes…) into panels. Three schema types cooperate:

PluginInstance

Defined as $defs/PluginInstance. Appears in panels.<id>.plugins[], inside a PluginLayer, and in variant overrides.

PropertyTypeRequiredDescription
pluginIdIdentifierYesWhich plugin to load.
instanceIdIdentifierYesUnique instance ID (a plugin can appear multiple times).
payloadIdIdentifierNoAsset catalog ID of a pluginPayload item holding the instance's data.
propsJsonValueNoArbitrary JSON configuration passed to the plugin.
sandbox"iframe" | "worker"NoRequested sandboxing model.
stateVariablesstring[] (unique)NoVariable IDs the plugin may read/write; each must match the pattern ^plugin\. (e.g. plugin.quiz.score).

Restricting plugins to the plugin. variable namespace keeps them from mutating story state they do not own. Wire plugin outcomes into the story with variables and conditional edges.

Plugins can also receive events from hotspots via the pluginEvent action (pluginId, event, optional payload).

PluginLayer

Defined as $defs/PluginLayer — a panel layer hosting a plugin. It extends the shared layer base (LayerCommon: id, z, opacity, visibleIf, transform, … — see Layers) with:

PropertyTypeRequiredDescription
kind"plugin"YesLayer discriminator.
pluginPluginInstanceYesThe plugin instance rendered by this layer.

AssetCatalogItemPluginPayload

Defined as $defs/AssetCatalogItemPluginPayload — an asset catalog entry carrying plugin data as JSON. Like all catalog items it inherits AssetCommon (id required; optional locale, alt, caption, transcript, durationMs, sha256, tags) plus:

PropertyTypeRequiredDescription
category"pluginPayload"YesCatalog discriminator.
variantsJsonVariant[] (min 1)YesEach variant: { "src": string, "mime": "application/json" }.

JsonValue

Defined as $defs/JsonValue — "any JSON value": null, boolean, number, string, an array of JsonValue, or an object whose values are JsonValue. Used for PluginInstance.props, mutation values, hotspot pluginEvent payloads, and variable defaults.

Example

A panel with a plugin layer whose data lives in the asset catalog:

{
  "assets": {
    "catalog": [
      {
        "id": "payload-quiz-ch1",
        "category": "pluginPayload",
        "variants": [
          { "src": "https://cdn.example.com/plugins/quiz-ch1.json", "mime": "application/json" }
        ]
      }
    ]
  },
  "chapters": [
    {
      "id": "ch-1",
      "panels": {
        "p-quiz": {
          "layers": [
            { "kind": "image", "id": "ly-bg", "assetId": "img-classroom", "z": 0 },
            {
              "kind": "plugin",
              "id": "ly-quiz",
              "z": 1,
              "plugin": {
                "pluginId": "quiz",
                "instanceId": "quiz-ch1-1",
                "payloadId": "payload-quiz-ch1",
                "props": { "shuffle": true, "maxAttempts": 2 },
                "sandbox": "iframe",
                "stateVariables": ["plugin.quiz.score", "plugin.quiz.completed"]
              }
            }
          ]
        }
      },
      "graph": { "entry": "p-quiz", "edges": [{ "from": "p-quiz", "to": "p-quiz" }] }
    }
  ]
}