Content Validation
Validation checks the structure of YAML and dialogue files and confirms that references point to existing content. It runs during development, before builds, and whenever you run it manually.
This page shows the commands and output for the CLI workflow. In Doodle Studio, the same checks run behind the Validate button, and Validate, Preview, and Build covers working through problems there.
When Validation Runs
Section titled “When Validation Runs”During Development (npm run dev)
Section titled “During Development (npm run dev)”When you run npm run dev, validation runs automatically whenever a content file is added, edited, or deleted:
npm run devErrors appear in the terminal while the development server continues running, so you can fix them without restarting it.
Example output:
✏️ Content changed: content/dialogues/bartender_greeting.dlg
✗ Found 1 validation error:
content/dialogues/bartender_greeting.dlg Node "greet" GOTO "continue" points to non-existent node Add NODE continue or fix the GOTO targetBefore Building (npm run build)
Section titled “Before Building (npm run build)”When you run npm run build, validation runs first. The release build begins after the content passes validation:
npm run buildExample output:
🐕 Building Doodle Engine game...
Validating content...
✗ Found 2 validation errors:
content/dialogues/bartender_greeting.dlg Node "greet" GOTO "continue" points to non-existent node Add NODE continue or fix the GOTO target
content/characters/merchant.yaml Character "merchant" references non-existent dialogue "merchant_chat" Create dialogue "merchant_chat" or fix the reference
Build failed due to validation errors.Manual Validation (npm run validate)
Section titled “Manual Validation (npm run validate)”You can run validation manually without starting the dev server or building:
npm run validateRun this command for:
- Quick content checks before committing
- Automated checks (validation returns exit code 1 on errors)
- Checking a specific group of changes
What Gets Validated
Section titled “What Gets Validated”Dialogue Parsing
Section titled “Dialogue Parsing”Validation parses each .dlg file, turning its text into dialogue data. If the syntax is invalid, the error names the file and the part that could not be read. Examples include an unknown keyword, condition, or effect, more than one speaker line in a node, a spoken line inside a choice, or a quoted value where the language expects a plain token.
content/dialogues/bartender_greeting.dlg Failed to parse dialogue: Node "start" (line 3) has more than one speaker line. Each node supports a single speaker; route to another NODE to let a different character speak. Fix the DSL syntax error in this .dlg fileDialogue Structure
Section titled “Dialogue Structure”- Start node exists: The
startNodespecified in a dialogue must be a valid node ID - No duplicate node IDs: Each node ID must be unique within its dialogue
- GOTO targets exist: All
node.next,choice.next, andconditionalBranches[].nexttargets must point to existing nodes. Exception: choices that containEND dialogueorGOTO locationdon’t need aGOTOtarget. They terminate the dialogue. - IF blocks are valid: Every conditional branch must have a valid condition, and any effects inside the branch must have their required arguments.
Example error:
content/dialogues/bartender_greeting.dlg Start node "invalid" not found Add a NODE invalid or fix the startNode referenceConditions
Section titled “Conditions”All conditions must have their required arguments:
| Condition | Required Arguments |
|---|---|
hasFlag, notFlag |
flag |
hasItem |
itemId |
questAtStage |
questId, stageId |
atLocation |
locationId |
characterAt |
characterId, locationId |
characterInParty |
characterId |
relationshipAbove, relationshipBelow |
characterId, value |
variableEquals, variableGreaterThan, variableLessThan |
variable, value |
characterStatEquals, characterStatGreaterThan, characterStatLessThan |
characterId, stat, value |
itemAt |
itemId, locationId |
timeIs |
startHour, endHour |
roll |
min, max, threshold |
Example error:
content/dialogues/bartender_greeting.dlg Node "ask_rumors" condition "hasFlag" missing required "flag" argumentEffects
Section titled “Effects”All node, choice, and IF branch effects must have their required arguments:
| Effect | Required Arguments |
|---|---|
setFlag, clearFlag |
flag |
setVariable, addVariable |
variable, value |
addItem, removeItem |
itemId |
moveItem |
itemId, locationId |
setQuestStage |
questId, stageId |
addJournalEntry |
entryId |
setCharacterLocation |
characterId, locationId |
addToParty, removeFromParty |
characterId |
setRelationship, addRelationship |
characterId, value |
setCharacterStat, addCharacterStat |
characterId, stat, value |
setMapEnabled |
enabled |
advanceTime |
hours |
goToLocation |
locationId |
startDialogue |
dialogueId |
playMusic |
(none; bare MUSIC clears override) |
playSound |
sound |
playVideo |
file |
showInterlude |
interludeId |
notify |
message |
endDialogue |
(none) |
roll |
variable, min, max |
Example error:
content/dialogues/bartender_greeting.dlg Node "greet" effect "setVariable" missing required "value" argumentCharacter Dialogue References
Section titled “Character Dialogue References”Characters’ dialogue field must reference existing dialogue IDs:
id: bartendername: 'Old Pete'dialogue: bartender_greeting # Must exist in content/dialogues/Example error:
content/characters/merchant.yaml Character "merchant" references non-existent dialogue "merchant_chat"Create dialogue "merchant_chat" or fix the referenceFiles and Required Fields
Section titled “Files and Required Fields”Every content file must load before its fields and references can be checked:
- A YAML file with a syntax error is reported by name, and the other files in its folder still load
- A YAML entity file must have an
id - Each
idis unique within its type. A clash is reported with both file names - An invalid
game.yamlis reported by name - Each entity must have the fields the engine reads: locations need
nameanddescription, characters needname, items neednameandlocation, maps needname, quests neednameand at least one stage, journal entries needtitleandtext, interludes needtext - Content IDs, dialogue node IDs, quest stage IDs, flags, and variables may contain only letters, numbers, and underscores
Content References
Section titled “Content References”Validation also checks IDs used by game config and built-in gameplay references:
game.yamlstartLocationmust point to an existing locationgame.yamlstartInventoryentries must point to existing items- Character starting locations must exist
- Item starting locations must be
inventory, an existing location, or an existing character - Dialogue speakers must be existing characters
- Dialogue and interlude trigger locations must exist
- Top-level dialogue
REQUIREconditions and interludetriggerConditionsare checked like any other condition - Built-in condition references must point to existing locations, items, characters, quests, and quest stages
- Built-in effect references must point to existing locations, items, characters, quests, quest stages, journal entries, dialogues, and interludes
For .dlg files, validation follows the condition and effect names documented in the references.
Numbers
Section titled “Numbers”Arguments that hold a number must contain a numeric value. For example,
ADD variable gold ten reports an error because ten is text.
Maps must reference existing locations, and scale must be greater than zero
(it turns marker distance into travel hours). A game can contain multiple
maps, but a location can only appear on one map. That keeps the current map
unambiguous: the engine shows the map that contains the player’s current
location.
Asset Files
Section titled “Asset Files”The asset manifest is the list of media files included with the game. Missing
images, audio, and video are reported when this list is created: during
npm run dev when the browser loads the game and at the start of every build.
npm run validate checks content and references. It does not scan media files.
Localization Keys
Section titled “Localization Keys”All @key references must exist in at least one locale file:
id: tavernname: '@location.tavern.name' # Must exist in locales/*.yamldescription: '@location.tavern.desc' # Must exist in locales/*.yamlExample error:
content/locations/tavern.yaml Localization key "@location.tavern.name" not found in any locale file Add "location.tavern.name: ..." to your locale filesCommon Validation Errors and Fixes
Section titled “Common Validation Errors and Fixes”GOTO Target Not Found
Section titled “GOTO Target Not Found”Error:
Node "greet" GOTO "continue" points to non-existent nodeCause: You referenced a node ID in GOTO that doesn’t exist.
Fix: Either create the missing node or fix the typo:
NODE greet Bartender: "Welcome to the tavern!" GOTO continue # Make sure this matches exactly
NODE continue # Add this node Bartender: "What can I get you?"Duplicate Node IDs
Section titled “Duplicate Node IDs”Error:
Duplicate node ID "greet"Cause: Two nodes have the same ID.
Fix: Rename one of the nodes:
NODE greet Bartender: "Welcome!"
NODE greet_again # Changed from "greet" Bartender: "Welcome back!"Missing Required Argument
Section titled “Missing Required Argument”Error:
Node "greet" condition "hasFlag" missing required "flag" argumentCause: A condition or effect is missing a required field.
Fix: Add the missing argument:
CHOICE "Ask about the quest" REQUIRE hasFlag quest_started GOTO ask_questENDCharacter References Non-Existent Dialogue
Section titled “Character References Non-Existent Dialogue”Error:
Character "merchant" references non-existent dialogue "merchant_chat"Cause: The character’s dialogue field points to a dialogue that doesn’t exist.
Fix: Either create the dialogue or fix the reference:
id: merchantname: 'Merchant'dialogue: merchant_intro # Fix: changed from merchant_chatLocalization Key Not Found
Section titled “Localization Key Not Found”Error:
Localization key "@location.tavern.name" not found in any locale fileCause: A @key reference doesn’t exist in any locale file.
Fix: Add the key to your locale files:
location.tavern.name: 'The Rusty Tankard'location.tavern.desc: 'A cozy tavern with warm firelight.'Automated Validation
Section titled “Automated Validation”Continuous integration (CI) services can run validation whenever changes are pushed:
name: Validate Content
on: [push, pull_request]
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - run: npm install - run: npm run validatenpm run validate returns exit code 1 when it finds errors, which tells the CI service that the check failed.
Working Habits That Help
Section titled “Working Habits That Help”Fix errors while the change is fresh: an error that appears in npm run dev seconds after you saved is trivial to trace, and the same mistake found a week later is not. Running npm run validate before committing keeps broken references out of version control.
Naming does a lot of quiet work here. Descriptive node, quest, and dialogue IDs make error messages readable on their own, and a consistent locale key convention such as entity_type.entity_id.field makes a missing key obvious at a glance. When a dialogue file grows unwieldy, splitting it also narrows down where errors point.
Validation and Playtesting
Section titled “Validation and Playtesting”Validation finds structural problems such as missing nodes, required arguments, and references to content that does not exist. Playtesting shows whether the story and rules behave as intended, including flag timing, dead ends, and text that needs revision. Use both before release.