Skip to content

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 you run npm run dev, validation runs automatically whenever a content file is added, edited, or deleted:

Terminal window
npm run dev

Errors 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 target

When you run npm run build, validation runs first. The release build begins after the content passes validation:

Terminal window
npm run build

Example 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.

You can run validation manually without starting the dev server or building:

Terminal window
npm run validate

Run this command for:

  • Quick content checks before committing
  • Automated checks (validation returns exit code 1 on errors)
  • Checking a specific group of changes

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 file
  • Start node exists: The startNode specified 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, and conditionalBranches[].next targets must point to existing nodes. Exception: choices that contain END dialogue or GOTO location don’t need a GOTO target. 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 reference

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" argument

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" argument

Characters’ dialogue field must reference existing dialogue IDs:

content/characters/bartender.yaml
id: bartender
name: '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 reference

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 id is unique within its type. A clash is reported with both file names
  • An invalid game.yaml is reported by name
  • Each entity must have the fields the engine reads: locations need name and description, characters need name, items need name and location, maps need name, quests need name and at least one stage, journal entries need title and text, interludes need text
  • Content IDs, dialogue node IDs, quest stage IDs, flags, and variables may contain only letters, numbers, and underscores

Validation also checks IDs used by game config and built-in gameplay references:

  • game.yaml startLocation must point to an existing location
  • game.yaml startInventory entries 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 REQUIRE conditions and interlude triggerConditions are 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.

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.

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.

All @key references must exist in at least one locale file:

content/locations/tavern.yaml
id: tavern
name: '@location.tavern.name' # Must exist in locales/*.yaml
description: '@location.tavern.desc' # Must exist in locales/*.yaml

Example error:

content/locations/tavern.yaml
Localization key "@location.tavern.name" not found in any locale file
Add "location.tavern.name: ..." to your locale files

Error:

Node "greet" GOTO "continue" points to non-existent node

Cause: 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?"

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!"

Error:

Node "greet" condition "hasFlag" missing required "flag" argument

Cause: 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_quest
END

Character 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:

content/characters/merchant.yaml
id: merchant
name: 'Merchant'
dialogue: merchant_intro # Fix: changed from merchant_chat

Error:

Localization key "@location.tavern.name" not found in any locale file

Cause: A @key reference doesn’t exist in any locale file.

Fix: Add the key to your locale files:

content/locales/en.yaml
location.tavern.name: 'The Rusty Tankard'
location.tavern.desc: 'A cozy tavern with warm firelight.'

Continuous integration (CI) services can run validation whenever changes are pushed:

.github/workflows/validate.yml
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 validate

npm run validate returns exit code 1 when it finds errors, which tells the CI service that the check failed.

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 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.