Narrative Template
Schema for narrative records. A narrative is a plan: a named story arc (e.g. 'problem-solution', 'scqa') as an ordered list of beats, each saying what its slide must do. A document points at one with the string 'narrative' and links its slides to beats with 'slides[].beat'; a narrative the document defines itself is a record in catalogs.custom.narratives. Core validate warns about a 'slides[].beat' id the resolved narrative does not define (opf/unknown-beat) and about a root 'duration' outside this record's 'duration' range (opf/duration-outside-narrative), and reports a beat no slide references (opf/unused-beat). A document references a record as a bare id or 'name:id' and resolves it in its own catalogs groups first, then in the catalog the host registered for the group's source; engines never fetch a catalog. A published record file carries '$schema' and 'id'; the copy a document embeds under catalogs.<group>.<kind>.<id> carries neither (the key is the id) and no 'x-*' display metadata.
Field index
| Field | Type | Req. | Default | Description |
|---|---|---|---|---|
| $schema | const | yes | — | Identifies a published record file as a narrative record. Required in a published file; an embedded copy omits it.
|
| id | string | yes | — | The narrative's id in its catalog, lowercase kebab-case. Required in a published record file; a document references the record by it (a bare id, or 'name:id' for a named group) and embeds it keyed by it, without this field. |
| name | string | yes | — | Human-readable template name, e.g. 'Problem → Solution'. |
| summary | string | — | — | One-sentence description of when and why to use this narrative. |
| description | string | — | — | Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction. |
| audienceFit | array<string> | — | — | Audiences this narrative works well for, e.g. ['executive', 'investor', 'customer']. |
| duration | object | — | — | Typical talk length this narrative suits, as a range in minutes. A deck's own target is the root 'duration' (one number); core validate warns when that target lies outside this range, and when 'min' is greater than 'max'. |
| tags | array<string> | — | — | Free-form labels for filtering and search, e.g. ['business', 'pitch', 'internal']. |
| preview | object | — | — | Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available. |
| beats | array<ref:Beat> | yes | — | Ordered list of beats that make up the narrative arc. |
$schema
typeconstrequiredyesdefault—
Identifies a published record file as a narrative record. Required in a published file; an embedded copy omits it.
$schema· Allowed values"https://openpresentation.org/schema/opf-narrative/v1"
{
"$schema": "https://openpresentation.org/schema/opf-narrative/v1"
}id
typestringrequiredyesdefault—pattern^[a-z][a-z0-9-]*$
The narrative's id in its catalog, lowercase kebab-case. Required in a published record file; a document references the record by it (a bare id, or 'name:id' for a named group) and embeds it keyed by it, without this field.
name
typestringrequiredyesdefault—
Human-readable template name, e.g. 'Problem → Solution'.
summary
typestringrequirednodefault—
One-sentence description of when and why to use this narrative.
description
typestringrequirednodefault—
Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction.
{
"description": "Open with the cost of slow agentic workflows, contrast with what becomes possible at sub-second latency, then walk through our architecture and benchmark results, ending with a concrete adoption ask."
}audienceFit
typearray<string>requirednodefault—
Audiences this narrative works well for, e.g. ['executive', 'investor', 'customer'].
duration
typeobjectrequirednodefault—
Typical talk length this narrative suits, as a range in minutes. A deck's own target is the root 'duration' (one number); core validate warns when that target lies outside this range, and when 'min' is greater than 'max'.
preview
typeobjectrequirednodefault—
Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available.
beats
typearray<ref:Beat>requiredyesdefault—
Ordered list of beats that make up the narrative arc.
beats[].type· Allowed values"text""list""image""video""chart""table""code""metric""quote""timeline"
Complete definition constraints
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openpresentation.org/schema/opf-narrative/v1",
"$comment": "The v1 in the $id is the pre-1.0 line, not a frozen contract: this schema may change in any 0.x minor release. The release that produced this copy is the version of the @openpresentation/opf package it came from (opf --version; opf schemas lists it next to each id). See docs/schema-versioning.md.",
"title": "Narrative Template",
"description": "Schema for narrative records. A narrative is a plan: a named story arc (e.g. 'problem-solution', 'scqa') as an ordered list of beats, each saying what its slide must do. A document points at one with the string 'narrative' and links its slides to beats with 'slides[].beat'; a narrative the document defines itself is a record in catalogs.custom.narratives. Core validate warns about a 'slides[].beat' id the resolved narrative does not define (opf/unknown-beat) and about a root 'duration' outside this record's 'duration' range (opf/duration-outside-narrative), and reports a beat no slide references (opf/unused-beat).\n\nA document references a record as a bare id or 'name:id' and resolves it in its own catalogs groups first, then in the catalog the host registered for the group's source; engines never fetch a catalog. A published record file carries '$schema' and 'id'; the copy a document embeds under catalogs.<group>.<kind>.<id> carries neither (the key is the id) and no 'x-*' display metadata.",
"type": "object",
"required": [
"$schema",
"id",
"name",
"beats"
],
"properties": {
"$schema": {
"type": "string",
"const": "https://openpresentation.org/schema/opf-narrative/v1",
"description": "Identifies a published record file as a narrative record. Required in a published file; an embedded copy omits it."
},
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$",
"description": "The narrative's id in its catalog, lowercase kebab-case. Required in a published record file; a document references the record by it (a bare id, or 'name:id' for a named group) and embeds it keyed by it, without this field."
},
"name": {
"type": "string",
"description": "Human-readable template name, e.g. 'Problem → Solution'."
},
"summary": {
"type": "string",
"description": "One-sentence description of when and why to use this narrative."
},
"description": {
"type": "string",
"description": "Longer prose describing the narrative arc and ideal use cases. Used by AI-driven generation to seed deck-level direction.",
"examples": [
"Open with the cost of slow agentic workflows, contrast with what becomes possible at sub-second latency, then walk through our architecture and benchmark results, ending with a concrete adoption ask."
]
},
"audienceFit": {
"type": "array",
"items": {
"type": "string"
},
"description": "Audiences this narrative works well for, e.g. ['executive', 'investor', 'customer']."
},
"duration": {
"type": "object",
"additionalProperties": false,
"description": "Typical talk length this narrative suits, as a range in minutes. A deck's own target is the root 'duration' (one number); core validate warns when that target lies outside this range, and when 'min' is greater than 'max'.",
"properties": {
"min": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Shortest talk length, in minutes, this narrative suits."
},
"max": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Longest talk length, in minutes, this narrative suits."
}
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Free-form labels for filtering and search, e.g. ['business', 'pitch', 'internal']."
},
"preview": {
"type": "object",
"description": "Visual previews of the record, used by picker UIs and inline rendering. All sub-fields are optional; engines fall back gracefully when previews aren't available.",
"properties": {
"src": {
"type": "string",
"format": "uri",
"description": "Main preview image (PNG/JPG). Used as the primary visual in picker UIs and previews of the record.",
"examples": [
"https://www.pptx.gallery/narratives/classic-story.png"
]
},
"thumbnailSrc": {
"type": "string",
"format": "uri",
"description": "Smaller thumbnail preview suited to dense grid views.",
"examples": [
"https://www.pptx.gallery/narratives/classic-story.thumbnail.png"
]
},
"vectorSrc": {
"type": "string",
"format": "uri",
"description": "SVG / vector preview for crisp scaling at any size.",
"examples": [
"https://www.pptx.gallery/narratives/classic-story.svg"
]
}
}
},
"beats": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/Beat"
},
"description": "Ordered list of beats that make up the narrative arc."
}
},
"$defs": {
"Beat": {
"type": "object",
"description": "A single narrative beat — a labeled segment of the story arc with a specific dramatic purpose. One beat is one slide: 'type' is that slide's Slide.type and 'layout' is its Slide.layout, so a planner copies them straight into a skeleton deck. Split a heavy beat into several beats rather than giving it several slides.",
"required": [
"id",
"name"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$",
"description": "Stable slug used by Slide.beat to reference this beat. Lowercase kebab-case.",
"examples": [
"opening",
"problem",
"evidence",
"ask",
"next-steps"
]
},
"name": {
"type": "string",
"description": "Human-readable beat name, e.g. 'The Problem'.",
"examples": [
"Opening",
"The Problem",
"Why Now",
"The Ask"
]
},
"description": {
"type": "string",
"description": "Curator-written prose that explains what this beat should accomplish.",
"examples": [
"Quantify the pain customers feel today, with one striking stat the audience can repeat afterward."
]
},
"instructions": {
"type": "string",
"description": "Short author-facing instruction for the beat — typically one phrase. Complements 'description' with a concise directive.",
"examples": [
"Capture audience attention",
"Introduce problem",
"Detail implementation",
"Inspire & conclude"
]
},
"type": {
"type": "string",
"enum": [
"text",
"list",
"image",
"video",
"chart",
"table",
"code",
"metric",
"quote",
"timeline"
],
"description": "Default content kind for the beat's slide: the same values as Slide.type. Helps engines and planners choose a layout when only the beat is known."
},
"layout": {
"type": "string",
"description": "Suggested layout id for the beat's slide, e.g. 'section-divider', 'title-slide', 'text-left'. Resolves the same way as Slide.layout — against catalogs.layouts and the default catalog at https://www.pptx.gallery/layouts.",
"examples": [
"section-divider",
"title-slide",
"title-left",
"two-column",
"text-left"
]
},
"thoughtCues": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional speaker or thinking cues for the beat: questions the slide should answer. A planner or author turns them into the slide's notes; no engine copies them there on its own.",
"examples": [
[
"What pain is the audience feeling right now?",
"Why hasn't anyone solved this yet?"
]
]
}
}
}
}
}Definition choices
Values declared by this schema and its nested definitions. Paths ending in [] describe array items; * describes each dictionary entry. Variant paths apply within the named schema branch.
$defs.Beat.type· Allowed values"text""list""image""video""chart""table""code""metric""quote""timeline"