Basic Campaign Example

A complete, simple campaign demonstrating core concepts: placeholders, quest actions, linear progression, and dynamic locations. Every field below is one the app actually reads - this would work if pasted into a real campaign file.

Verified against reality: uses name (not title), levelRange as an object, episodeNumber + title on the episode, and the real <div class='read-aloud'> wrapper on every readAloud string. There is no top-level quests array with a status field - quests live in each episode's questTemplates.

Campaign Overview

The Bandit Threat

Level: 1-3

Duration: 2-3 sessions

Synopsis: Bandits are threatening the local area. The party must track them down and defeat them before they strike again.

Features Demonstrated:

  • Basic placeholder usage ({nearestBurg}, {nearestDungeon})
  • Quest acceptance and completion
  • Linear scene progression
  • Simple choices
  • Scope control (local adventure)

Complete Campaign JSON

{ "id": "bandit-threat", "name": "The Bandit Threat", "description": "A simple adventure where heroes must stop bandits threatening the local settlement.", "levelRange": { "min": 1, "max": 3 }, "episodes": [ { "id": 1, "episodeNumber": 1, "title": "The Threat Emerges", "description": "Bandits attack a merchant near town", "questTemplates": [ { "name": "Stop the Bandits", "type": "combat", "description": "Defeat the bandit group threatening {nearestBurg}", "rewards": { "gold": 100, "xp": 300 } } ], "scenes": [ { "sceneId": "ep1-scene1", "title": "Trouble in Town", "readAloud": "<div class='read-aloud'><p>You stand in the market square of {nearestBurg}, a small frontier settlement. The morning bustle is interrupted by shouts - a bloodied merchant stumbles through the gates.</p><p>'Bandits!' he gasps. 'They attacked my caravan just outside town!'</p></div>", "choices": [ { "text": "Help the merchant", "nextScene": "ep1-scene2" }, { "text": "Ignore him and continue shopping", "nextScene": "ep1-scene1-ignore" } ] }, { "sceneId": "ep1-scene1-ignore", "title": "A Missed Opportunity", "readAloud": "<div class='read-aloud'><p>You turn away, but the town guard quickly surrounds you. 'We need all able-bodied adventurers,' the captain says firmly. 'This is a town emergency.'</p></div>", "choices": [ { "text": "Agree to help", "nextScene": "ep1-scene2" } ] }, { "sceneId": "ep1-scene2", "title": "The Quest", "readAloud": "<div class='read-aloud'><p>The town elder, Gareth, approaches you with a worried expression.</p><p>'These bandits have been raiding for weeks. If we don't stop them, {nearestBurg} won't survive the winter. Will you help us?'</p></div>", "choices": [ { "text": "Accept the quest", "nextScene": "ep1-scene3", "questActions": ["accept:Stop the Bandits"] }, { "text": "Decline (quest ends)", "nextScene": "ep1-scene2-decline" } ] }, { "sceneId": "ep1-scene2-decline", "title": "Not Your Problem", "readAloud": "<div class='read-aloud'><p>Gareth's face falls. 'I understand,' he says quietly. 'We'll have to manage on our own.'</p><p>You leave {nearestBurg} behind, but the memory of their desperate faces haunts you.</p><p><strong>Campaign Ended</strong></p></div>", "choices": [] }, { "sceneId": "ep1-scene3", "title": "The Investigation", "readAloud": "<div class='read-aloud'><p>You examine the attack site. Tracks lead north toward the hills. The bandits aren't being subtle - they want to be found.</p></div>", "choices": [ { "text": "Follow the tracks", "nextScene": "ep1-scene4" }, { "text": "Return to {nearestBurg} to gather information", "nextScene": "ep1-scene3-investigate" } ] }, { "sceneId": "ep1-scene3-investigate", "title": "Gathering Intel", "readAloud": "<div class='read-aloud'><p>The townsfolk tell you the bandits have been using {nearestDungeon:type=Cave} as a hideout. Armed with this knowledge, you set out.</p></div>", "choices": [ { "text": "Travel to the hideout", "nextScene": "ep1-scene4" } ] }, { "sceneId": "ep1-scene4", "title": "The Bandit Hideout", "readAloud": "<div class='read-aloud'><p>You arrive at {nearestDungeon:type=Cave}. Smoke rises from the entrance - they're inside.</p><p>You can hear voices and the clatter of weapons. About 8 bandits, by your count: {monster:Bandit|count=8}.</p></div>", "dmNotes": { "enemies": { "bandits": { "count": 8, "hp": 11, "ac": 12, "tactics": "Fight in the open, flee if reduced to half their number" } } }, "choices": [ { "text": "Attack directly", "nextScene": "ep1-scene5-combat" }, { "text": "Attempt to sneak in", "nextScene": "ep1-scene5-stealth" }, { "text": "Try to negotiate", "nextScene": "ep1-scene5-negotiate" } ] }, { "sceneId": "ep1-scene5-combat", "title": "The Battle", "readAloud": "<div class='read-aloud'><p>You charge in, weapons drawn. The bandits are caught off-guard.</p><p>After a fierce battle, the last bandit falls. Their threat to {nearestBurg} is ended.</p></div>", "choices": [ { "text": "Return to {nearestBurg} victorious", "nextScene": "ep1-scene6-victory" } ] }, { "sceneId": "ep1-scene5-stealth", "title": "The Ambush", "readAloud": "<div class='read-aloud'><p>You creep through the shadows, catching the bandits unaware.</p><p>After defeating the bandits, you search the cave for stolen goods.</p></div>", "dmNotes": { "skillChecks": { "stealth_dc_12": "Success grants a surprise round in the combat that follows; failure proceeds to combat normally." } }, "choices": [ { "text": "Return to {nearestBurg} with the recovered goods", "nextScene": "ep1-scene6-victory" } ] }, { "sceneId": "ep1-scene5-negotiate", "title": "Failed Diplomacy", "readAloud": "<div class='read-aloud'><p>'Negotiate?' the bandit leader laughs. 'We don't negotiate with dead men!'</p><p>They attack. Roll for initiative.</p></div>", "choices": [ { "text": "Fight your way out", "nextScene": "ep1-scene5-combat" } ] }, { "sceneId": "ep1-scene6-victory", "title": "Heroes' Welcome", "readAloud": "<div class='read-aloud'><p>You return to {nearestBurg} as heroes. The townsfolk cheer your arrival.</p><p>Gareth greets you with tears in his eyes. 'You've saved us all,' he says, handing you a pouch of gold. 'This is everything we can spare - you've earned it.'</p></div>", "choices": [ { "text": "Accept the reward", "nextScene": "ep1-scene7-end", "questActions": ["complete:Stop the Bandits"] } ] }, { "sceneId": "ep1-scene7-end", "title": "A New Dawn", "readAloud": "<div class='read-aloud'><p>With the bandits defeated, peace returns to {nearestBurg}. The townsfolk can sleep safely once more.</p><p>Your names will be remembered here for years to come.</p><p><strong>Campaign Complete!</strong></p></div>", "choices": [] } ] } ] }

Key Features Explained

1. Dynamic Locations

Notice how the campaign uses placeholders instead of hardcoded names:

{nearestBurg} // Home base
{nearestDungeon:type=Cave} // Bandit hideout

This works for any party - Campaign Cookbook generates a fresh set of named towns per party+campaign, so the same campaign JSON adapts automatically without you hardcoding a town name.

2. Quest Lifecycle

The quest follows the standard accept → complete pattern:

3. Linear with Branches

The main story is linear (Scene 1 → 2 → 3 → 4 → 5 → 6 → 7), but offers tactical branches:

4. Meaningful Choices

Choices affect approach but not outcome - all paths lead to victory:

Customization Ideas

Add Failure Path

Create consequences for declining or failing:

{ "text": "Flee from the bandits", "nextScene": "ep1-scene5-flee", "questActions": ["fail:Stop the Bandits"] }

Add More Locations

Use additional placeholders for variety:

"The bandits are also raiding {randomBurg:maxPopulation=300}."

Add Side Quests

Add another entry to the episode's questTemplates array - there's no "hidden"/"available" status field, a quest simply exists in the array and becomes active whenever a scene's questActions accepts it:

{ "name": "Rescue the Captives", "type": "side-quest", "description": "Free prisoners held by the bandits", "rewards": { "gold": 50, "xp": 150 } }

Testing Checklist

What to Try Next