Variables & Conditions
Define and test story variables in the PanelWave CMS — variable types and scopes, setting values from hotspots, building conditions, and preview overrides.
Variables are your story's memory. They remember what the reader did — which door they opened, how much a character trusts them, whether they have found the key — and conditions read that memory to decide what happens next: which panel comes after this one, which layers are visible, which speech bubble appears.
This page covers how variables work in the CMS editor. For the concept itself see Variables; for the exact format see Variables in the schema.
Anatomy of a variable
Every variable is defined once per work, with:
| Property | What it is |
|---|---|
| Id | The name you use in conditions, e.g. trustLevel or inventory.hasKey. Letters and digits, optionally in dot-separated segments. |
| Type | What kind of value it holds (see below). |
| Default | The value every reader starts with. |
| Scope | How long the value lives (see below). |
| Read-only | If set, the story can read but not change the value. |
| Visibility | public (default) or private. |
| Description | A note to yourself about what the variable is for. |
Types
| Type | Holds | Example |
|---|---|---|
boolean | true / false | hasKey: true |
number | Any number | trustLevel: 62.5 |
integer | Whole numbers | score: 100 |
string | Text | playerName: "Aki" |
enum | One value from a fixed list | difficulty: "easy" | "normal" | "hard" |
date / time / datetime | Calendar and clock values | firstReadAt |
The five scopes
Scope controls when a variable's value resets:
| Scope | Lifetime |
|---|---|
global | Lives for the whole work, across all chapters. |
chapter | Resets when the reader enters a new chapter. |
page | Resets when the reader moves to a new page. |
session | Lives for one reading session; gone when the reader closes the story. |
persistent | Survives across sessions — the reader can come back tomorrow and the value is still there. |
Rule of thumb: story decisions the whole work should remember → global or persistent; short-lived puzzle state → chapter or page; things like "has seen the tutorial hint this visit" → session.
The Variables designer
Variables are defined per work in the Variables designer: open a work's Backstage and choose Variables in the sidebar (the editor's work-areas menu has the same entry). The page lists every definition in a table — id, type, default, scope, read-only/private flags, and description — with a search box and type/scope filters above it.
Create a variable
Click New Variable. Enter the Id (letters, digits and underscores such as trust_jonas, optionally in dot-separated segments such as inventory.hasKey), pick the Type and Scope, and optionally a Default value, Visibility, the Read-only flag, and a Description. For an enum, list the Allowed values one per line; the default must be one of them.
Save
Click Save Variable. The dialog validates the definition first (a malformed id, a duplicate id, a default that doesn't match the type) and points at the problem; the CMS re-checks the same rules on the server, so what you save is always valid against the schema.
Edit, duplicate, delete
Use the row actions. Changing a type resets its default so a stale value can't be carried over. Deleting a variable does not touch the conditions or hotspot actions that mention it — they simply stop resolving, so search for the id first.
Changes are saved immediately and land in the manifest on the next publish, in the Preview page's variable overrides, and in the player's variable store.
Import and export
- Export JSON downloads the definitions as a manifest fragment (
{ "variables": { "definitions": [ … ] } }) that you can paste into a hand-maintainedpanelwave.jsonor keep as a backup. - Import JSON accepts the same fragment, a plain
{ "definitions": [ … ] }object, or a bare array, and replaces the current list after a confirmation. - Importing a whole manifest (or a work archive) through Import keeps all definitions of its
variablessection; entries that don't pass validation are skipped and logged.
A definition looks like this in the manifest:
{
"variables": {
"definitions": [
{
"id": "trustLevel",
"type": "integer",
"default": 50,
"scope": "global",
"description": "How much Mira trusts the reader"
},
{
"id": "difficulty",
"type": "enum",
"enum": ["easy", "normal", "hard"],
"default": "normal",
"scope": "session"
}
]
}
}
Give ids a namespace that says where they belong (story.choice, stats.trust, inventory.hasKey, prefs.hints) and use an enum for any choice with a fixed set of outcomes — a typo in a condition then fails loudly instead of silently never matching.
Changing values from the story
Readers change variables through hotspot actions. In the Hotspots inspector, click + Add Action and choose the action type Set Variables. Each row of the action has three parts:
| Field | Options | Example |
|---|---|---|
| Variable name | Any variable id | trustLevel |
| Operation | Set, Increment, Toggle | Increment |
| Value | The value to set / add | 10 |
So "clicking this door increases trustLevel by 10" is: variable trustLevel, operation Increment, value 10. Use Toggle for booleans (hasKey flips between true and false) and Set to assign a value outright. One action can change several variables at once — click + Add Variable for more rows.
Using variables in conditions
Conditions appear in three places in the editor, with two styles of input.
1. Edge conditions in the Graph Editor (JSON Logic)
Branching is decided on graph edges. When you select an edge in the Graph Editor, the Edge panel offers a Condition (JSON Logic; empty = always follow) field where you type the rule as JSON.
JSON Logic looks intimidating for about five minutes, then the pattern clicks. A rule is always:
{"OPERATOR": [LEFT SIDE, RIGHT SIDE]}
- To read a variable, write
{"var": "name"}. - Everything else is a plain value:
100,"easy",true.
{"==": [{"var": "difficulty"}, "easy"]}
True when difficulty is "easy".
{">": [{"var": "score"}, 100]}
True when score is above 100.
{"and": [
{"==": [{"var": "hasKey"}, true]},
{">=": [{"var": "trustLevel"}, 75]}
]}
True when the reader has the key and trust is at least 75.
{"or": [
{"==": [{"var": "chosenPath"}, "heroic"]},
{">": [{"var": "score"}, 500]}
]}
True when the reader chose the heroic path or scored over 500.
The comparison operators are ==, !=, <, >, <=, >=, combined with and, or, and not. That small set covers nearly every story rule. The same JSON Logic format powers conditions everywhere in the PanelWave format, so what you learn here transfers.
2. Visibility conditions with the Condition Builder
Hotspots and speech bubbles have a Visibility Condition section in the inspector: an element with a condition only appears when the condition is true. You can type the expression directly, or click Build Condition to open the Condition Builder dialog — no code required:
Add a condition
Click + Add Condition. Each condition card has four inputs: Variable (the name), Type (String / Number / Boolean), Operator, and Value.
Pick the operator
All types offer equals and not equals. Numbers add greater than, less than, greater or equal, less or equal; strings add contains, starts with, ends with; booleans use a simple true/false dropdown.
Combine multiple conditions
With two or more cards, choose AND (all must be true) or OR (any can be true) at the top.
Check the preview and apply
The Expression Preview shows the result as readable text, e.g. score > 100 && hasKey === true. Click Apply Condition to save it to the element.
3. Mutation triggers
Hotspot mutations change an element's look (Visible, Opacity, Scale, Rotation, Position, Color) in response to a trigger — On Hover, On Click, or When Variable Changes. The variable trigger takes a Condition Expression in the same style as the Condition Builder output, e.g. hasKey === true or score > 100, so artwork can react the moment your story state changes.
Testing values in Preview
The Preview page has a Variables: section that lists every variable defined in your work, each with an editor that matches its type — a checkbox for booleans, a dropdown for enums, number/date/time inputs where appropriate. For each variable you see:
- its id, type badge, and the default value,
- a read-only badge where applicable,
- an overridden badge once you change the value, and a reset button to return to the default.
Override a few values, then read the chapter in the embedded player to check that your branches, visibility conditions, and mutations fire the way you intended. Overrides are part of preview scenarios, so you can save "Premium reader with the key" or "Fresh first-time reader" as one-click test setups.
Test both sides of every branch: once with values that satisfy the condition and once with values that don't. The most common branching bug is a condition that can never be true because the variable is never set — search your hotspots for the matching Set Variables action.