PajamaScript: Interactive Story Format Specification
Version: 1.1
Last Updated: 2025-05-11
π Overviewβ
PajamaScript is a structured, JSON-based storytelling format for defining interactive experiences in Pajama. It organizes a story into a set of pages. Stories can flow linearly or branch via choice pages, enabling interactive narrative structure.
It is designed to be both LLM-friendly and human-authorable.
π¦ Story-Level Schemaβ
{
"schema_version": int,
"id": string,
"title": string,
"start_page_id": string,
"pages": [string]
}
Each story defines its entry point (start_page_id) and a list of pages.
π StoryPage Objectβ
A StoryPage represents a single page of the story. All pages share the same base schema:
{
"id": "<uuid>",
"type": "title | text | choice | ending",
"image_url": "castle.jpg" | null,
"tools": [Tool]
}
Argsβ
id(string) β An identifier for the page, must be unique only for the current story.type(string) β Determines the type of content displayed on the page:"title"β A page that presents the title page of the story."text"β A page that presents narrative content line-by-line."choice"β A page that presents a set of choices that lead to different outcomes"ending"β A page that presents an ending message.
image_url(string, optional) β An image associated with this page. Can benullor omitted if no image is available.tools(list[Tool]) β Optional tools to activate on this page.
Titleβ
The first page of a story.
{
"id": "<uuid>",
"type": "text",
"header": "Little Red Riding Hood",
"subheader": "By R.L. Stein" | null,
"next_page_id": "character_intro_page",
"tools": [Tool],
}
Argsβ
header(string) β A header line of textsubheader(string or null, optional) β A subheader line of textnext_page_id(string) - Specifies the next page after the title page.
Textβ
Used for displaying one ore more lines of narrative content.
{
"id": "<uuid>",
"type": "text",
"text_lines": ["Once upon a time", "in a land far, far away"],
"text_positioning": "separate",
"text_multiline_entry_strategy": "all",
"text_newline_delay_ms": -1,
"next_page_id": "castle_intro_page" | null,
"tools": [Tool],
}
Argsβ
text_lines(list[string]) β Story text to display, line-by-line.text_positioning(string) β Where the text appears on screen:"separate"β A split-screen UI with text below any available image and video feeds."overlay"β Text is positioned on top of any available image.
text_multiline_entry_strategy(string) β"all"β All lines appear at once when the page loads."one_by_one_replace"β Lines appear one at a time, replacing the previous lines."one_by_one_append"β Lines appear one at a time, appended below previous lines.
text_newline_delay_ms(int) β Delay (in milliseconds) between displaying lines when using a one-by-onetext_multiline_entry_strategy. If-1, waits for the user to manually tap to show the next line of text.next_page_id(string or null, optional) - Specifies the next page after this page. Ifnullor omitted, the story ends.
Choiceβ
Used to branch the story based on a user decision.
{
"id": "<uuid>",
"type": "choice",
"header": "Who will you choose to help?" | null,
"choices": [
{ "label": "Fiona", "next_page_id": "fiona_castle" },
{ "label": "Shrek", "next_page_id": "shrek_swamp" },
{ "label": "Donkey", "next_page_id": "donkey_waffle_house" },
],
"tools": [Tool],
}
Argsβ
header(string, optional) β A header to display above the choices.choices(list[object]) β List of available choices:"label"β A human-readable text label for this choice."next_page_id"β The page to navigate to if this choice is selected.
Endingβ
The last page of a story. The reader client will usually display buttons to re-read or exit the story.
{
"id": "<uuid>",
"type": "text",
"header": ["The End"],
"tools": [Tool],
}
Argsβ
header(list[string]) β A header line of text
π Tool Objectβ
A Tool adds interactivity to a page such as visual or audio effects. All tools follow the same schema:
{
"name": "<tool_name>",
"version": 1,
"scope": "page | story",
"args": { ... }
}
Argsβ
name(string) β A unique identifier for the tool.version(int) β The tool schema version. Increment this if the toolβs interface changes.scope(string) β Determines the tool's lifespan:"page"β The tool is active only for the current page."story"β The tool remains active for the remainder of the story or until explicitly ended on a later page.
args(object) β Tool-specific arguments.
Notesβ
- Multiple tools can be used per page.
π Supported Toolsβ
ar_maskβ
Applies an augmented reality mask to one or more participants.
{
"name": "ar_mask",
"version": 1,
"scope": "page | story",
"args": {
"target": "reader | listeners | all",
"mask_id": "volcano_hat"
}
}
Argsβ
target(string) β Specifies which participants receive the mask."reader"β The participant reading the story who has control over page flips."listeners"β All participants who are not the reader."all"β All participants.mask_id(string) β A unique identifier for the AR mask asset to render.
Notesβ
- Multiple AR masks can be enabled simultaneously, but each one must use a different
target.
sound_effectβ
Plays a short sound effect triggered by a page event or user interaction.
{
"name": "sound_effect",
"version": 1,
"scope": "page | story",
"args": {
"sound_url": "drum_roll",
"trigger": "page_enter | first_page_tap | every_page_tap",
"broadcast_taps": true,
"delay_ms": 500
}
}
Argsβ
sound_url(string) β URL of the sound effect to play. Examples:"magic_twinkle","frog_ribbit","drum_roll".trigger(string) β When the sound should play:"page_enter"β Immediately when the page finishes loading."first_page_tap"β The first time the user taps the page."every_page_tap"β Every time the user taps the page.
broadcast_taps(boolean) β Iftrue, the sound plays for everyone in the call when tapped. Iffalse, the sound is only played locally for the tapper. Ignored iftriggeris not a tap.delay_ms(int) β Delay (in milliseconds) aftertriggerbefore playing the sound effect.
Notesβ
- Only one sound effect may be enabled for a page. If
scopeis"page", it overrides any existing sound effects.
background_musicβ
Plays a background music track.
{
"name": "background_music",
"version": 1,
"scope": "page | story",
"args": {
"sound_url": "general_adventure",
"loop": true
}
}
Argsβ
sound_url(string) β URL of the background music file. Examples:"spooky_castle","mysterious_corridor","general_adventure".loop(boolean) β Iftrue, the music loops continuously for the duration of thescope. Iffalse, playback ends when the track finishes.
Notesβ
- Only one background music may be enabled for a page. If
scopeis"page", it overrides any existing background music.
π Example PajamaScriptβ
The payload when fetching a story from /story/luna-moon-adventure-v1.1:
{
"schema_version": 1,
"id": "luna-moon-adventure-v1.1",
"title": "Luna's Magical Moon Adventure",
"start_page_id": "intro_page-1",
"page_ids" [
]
}
The payload when fetching pages from /story/luna-moon-adventure-v1.1/page:
[
{
"id": "intro-page-1",
"type": "text",
"image_url": "assets/images/luna_looking_at_moon.png",
"text_lines": [
"Luna loved the moon.",
"Tonight, it seemed to shine just for her!"
],
"text_positioning": "separate",
"text_multiline_entry_strategy": "one_by_one_append",
"text_newline_delay_ms": 2000,
"tools": [
{
"name": "background_music",
"version": 1,
"scope": "story",
"args": {
"sound_url": "assets/music/dreamy_night_theme.mp3",
"loop": true
}
}
],
"next_page_id": "intro-page-2",
},
{
"id": "intro-page-2",
"type": "text",
"image_url": "assets/images/moonbeam_window.png",
"text_lines": [
"Suddenly, a moonbeam shimmered through her window...",
"...and a sparkling ladder appeared!"
],
"text_positioning": "overlay",
"text_multiline_entry_strategy": "one_by_one_replace",
"text_newline_delay_ms": 2500,
"tools": [
{
"name": "sound_effect",
"version": 1,
"scope": "page",
"args": {
"sound_url": "assets/sfx/magic_twinkle_long.mp3",
"trigger": "page_enter"
}
}
],
"next_page_id": "moon-page-1"
},
{
"id": "moon-page-1",
"type": "text",
"image_url": "assets/images/luna_on_moon.png",
"text_lines": [
"Whoosh! Luna found herself on the moon!",
"It was bouncy and made of cheese (or so she thought)!"
],
"text_positioning": "separate",
"text_multiline_entry_strategy": "all",
"tools": [
{
"name": "ar_mask",
"version": 1,
"scope": "page",
"args": {
"target": "reader",
"mask_id": "astronaut_helmet_simple"
}
},
{
"name": "sound_effect",
"version": 1,
"scope": "page",
"args": {
"sound_url": "assets/sfx/boing.mp3",
"trigger": "first_page_tap",
"broadcast_taps": false // Only local boing for the tapper
}
}
]
"next_page_id": "moon-page-2"
},
{
"id": "moon-page-2",
"type": "choice",
"image_url": "assets/images/moon_critters_choice.png",
"choice_header": "She saw two paths. Which way should she explore?",
"choices": [
{ "label": "The Sparkly Crystal Cave", "next_page_id": "cave-page-1" },
{ "label": "The Giggling Moon Bunnies", "next_page_id": "bunny-page-1" }
],
"tools": [] // Story-scoped music "dreamy_night_theme" continues
},
{
"id": "cave-page-1",
"type": "text",
"image_url": "assets/images/crystal_cave_bright.png",
"text_lines": ["The cave was full of sparkling crystals!", "They hummed with a soft light."],
"text_positioning": "overlay",
"text_multiline_entry_strategy": "one_by_one_append",
"text_newline_delay_ms": -1,
"tools": [
{ // Page-scoped music overrides the story-scoped music
"name": "background_music",
"version": 1,
"scope": "page",
"args": { "sound_url": "assets/music/crystal_cave_shimmer.mp3", "loop": true }
}
],
"next_page_id": "conclusion-page-1"
},
{
"id": "bunny-page-1",
"type": "text",
"image_url": "assets/images/moon_bunnies_playing_luna.png",
"text_lines": ["The moon bunnies were so fluffy and playful!", "They shared their moon carrots with Luna."],
"text_positioning": "separate",
"text_multiline_entry_strategy": "all",
"tools": [
// "dreamy_night_theme" (story-scoped) continues here as no page-scoped music overrides it.
{
"name": "sound_effect",
"version": 1,
"scope": "page",
"args": { "sound_url": "assets/sfx/bunny_giggle_soft.mp3", "trigger": "page_enter", "broadcast": true }
}
],
"next_page_id": "conclusion-page-1"
},
{
"id": "conclusion-page-1",
"type": "text",
"image_url": "assets/images/luna_waving_goodbye_on_moon.png",
"text_lines": [
"What an amazing adventure!",
"Luna knew she'd never forget her trip to the moon."
],
"text_positioning": "separate",
"text_multiline_entry_strategy": "one_by_one_append",
"text_newline_delay_ms": 3000,
"tools": [
{ // Stop the story-scoped "dreamy_night_theme"
"name": "background_music",
"version": 1,
"scope": "page", // Stop it just for this page
"args": { "sound_url": "none" } // Special value to stop music
},
{
"name": "sound_effect",
"version": 1,
"scope": "page",
"args": {
"sound_url": "assets/sfx/gentle_chime_end.mp3",
"trigger": "page_enter",
"delay_ms": 500
}
}
]
// next_page_id is omitted so this signifies the END OF THE STORY.
}
]