Variables
Variables are named constants declared in a top-level variables: section. They are substituted into definitions and operations during preprocessing, before YAML deserialization.
Declaring variables
Section titled “Declaring variables”variables: BASE_HP: 1000 BASE_MP: 500 ENCHANT_STEPS: [10, 11, 12]Variable names must match [A-Za-z_][A-Za-z0-9_]*. Reserved names (extends, remove, with, params, loop, repeat) are not allowed.
Variable types
Section titled “Variable types”| Type | Example | Substitution result |
|---|---|---|
| Scalar (string, number, bool) | BASE_HP: 1000 | $BASE_HP → 1000 |
| Scalar list | STEPS: [10, 11, 12] | $STEPS → [10, 11, 12] |
| Forbidden type | Example | Error |
|---|---|---|
| Mapping | STATS: { hp: 100, mp: 50 } | E534 |
| List of mappings | ITEMS: [{ id: 1 }, { id: 2 }] | E534 |
| List of lists | MATRIX: [[1,2],[3,4]] | E534 |
Substitution
Section titled “Substitution”Variables are referenced with a $ prefix. Only whole-value replacement is supported — the entire scalar value must be the variable reference.
variables: DEFAULT_LEVEL: 60
creatures: create: - id: 1 level: $DEFAULT_LEVEL name: goblinAfter resolution: level: 60. The variables: section is stripped from output.
Variables in definitions
Section titled “Variables in definitions”Variables declared in variables: are available inside definition bodies. Substitution runs after $extends merge:
variables: BASE_HP: 1000
definitions: warrior: hp: $BASE_HP mp: 500
warriors: create: - $extends: warrior name: fighterAfter resolution: hp: 1000, mp: 500.
Cross-module variables
Section titled “Cross-module variables”Exporting
Section titled “Exporting”Declare variables and list them in exports.variables:
variables: BASE_HP: 1000 BASE_MP: 500
exports: variables: - BASE_HP - BASE_MPAll exported names must exist in the module’s variables: section or its effective imported scope (E535 if not). See Re-exporting for details.
Importing
Section titled “Importing”Import variables with use.variables in the import directive:
imports: - from: shared/constants.yaml use: variables: - BASE_HP - BASE_MP
warriors: create: - id: 1 hp: $BASE_HP mp: $BASE_MPVariables require explicit opt-in via use.variables. An import without use.variables does not import any variables, even if the source module exports them.
Requesting a variable not listed in the source module’s exports.variables triggers E536.
Re-exporting
Section titled “Re-exporting”A module can re-export variables it has imported via use.variables. The re-exported variable becomes part of the module’s public export surface, allowing downstream consumers to import it without depending on the original source:
# package-a/index.yml — declares and exports BASE_HPvariables: BASE_HP: 1000
exports: variables: - BASE_HP# package-b/index.yml — imports and re-exports BASE_HPimports: - from: package-a use: variables: - BASE_HP
exports: variables: - BASE_HP # Re-export: allowed because BASE_HP is in imported scope# consumer.yaml — imports from package-b, not package-aimports: - from: package-b use: variables: - BASE_HP # Works: package-b re-exports it
items: create: - id: 1 hp: $BASE_HP # Resolves to 1000A module can only re-export a variable that it explicitly imported via use.variables. Attempting to re-export a variable that was not imported triggers E535:
# INVALID: package-b did not import INTERNAL via use.variablesimports: - from: package-a # No use.variables — no variables imported
exports: variables: - INTERNAL # E535: not in local variables or effective imported scopeWhen a module declares a local variable with the same name as an imported one, the local value is exported:
imports: - from: package-a use: variables: - HP # HP: 100 from package-a
variables: HP: 999 # Local declaration shadows the import
exports: variables: - HP # Exports 999 (local value wins)Package imports
Section titled “Package imports”Variables can be imported from registered packages:
imports: - from: shared use: variables: - GLOBAL_HPThe package’s index.yml must export the variable.
Local shadowing
Section titled “Local shadowing”Local variables shadow imported variables of the same name:
imports: - from: shared/constants.yaml use: variables: - BASE_HP
variables: BASE_HP: 9999
warriors: create: - id: 1 hp: $BASE_HP # Resolves to 9999, not the imported 1000Combined imports
Section titled “Combined imports”Variables and definitions can be imported from the same module:
imports: - from: shared/base.yaml use: variables: - DEFAULT_LEVEL
creatures: create: - $extends: baseCreature id: 1 name: goblinDefinitions import automatically (no opt-in needed). Variables require use.variables.
Where variables resolve
Section titled “Where variables resolve”| Context | Resolved? | Notes |
|---|---|---|
| Definition bodies | Yes | After $extends merge |
| Spec body (create, update, upsert) | Yes | Direct substitution |
$with values | Yes | $with: { X: $VAR } resolves $VAR before binding |
$extends value (with $with) | Yes | $extends: $PARAM resolves via $with bindings |
$extends value (without $with) | No | Direct $extends: $VAR is not supported |
Limitations
Section titled “Limitations”| Constraint | Detail |
|---|---|
| No inline interpolation | "prefix_${VAR}_suffix" does not work. Only full-value replacement. |
No direct $extends: $VAR | $extends requires a literal definition name — unless $with bindings provide the value. See Definitions. |
| No expressions | ${ BASE_HP + 100 } is not supported. |
| No namespaces | Variable names are flat — no dot-separated paths. |
| Flat names only | Casing is not enforced; [A-Za-z_][A-Za-z0-9_]* is the only constraint. |
For parameterized templates, use definitions with $extends and $with.
Error codes
Section titled “Error codes”| Code | Meaning |
|---|---|
| E520 | Unknown variable reference ($MISSING with no binding in scope) |
| E532 | Invalid variable name (bad pattern or reserved name) |
| E533 | Duplicate variable declaration in the same module |
| E534 | Invalid variable type (mapping or non-scalar list) |
| E535 | Exported variable not found in module’s variables: section or effective imported scope |
| E536 | Imported variable not listed in source module’s exports.variables |
E520: Unknown variable reference
Section titled “E520: Unknown variable reference”variables: HP: 100
creatures: create: - id: 1 hp: $MISSING # E520: No variable 'MISSING' in scopeE532: Invalid variable name
Section titled “E532: Invalid variable name”variables: extends: foo # E532: 'extends' is a reserved namevariables: 123bad: foo # E532: Does not match [A-Za-z_][A-Za-z0-9_]*E534: Invalid variable type
Section titled “E534: Invalid variable type”variables: STATS: hp: 100 mp: 50 # E534: Mapping values are not allowedE535: Exported variable not found
Section titled “E535: Exported variable not found”Triggers when an exported variable name is neither declared locally in variables: nor available in the module’s effective imported scope (via use.variables):
variables: BASE_HP: 1000
exports: variables: - BASE_HP - NONEXISTENT # E535: not in local variables or effective imported scopeE536: Imported variable not exported
Section titled “E536: Imported variable not exported”imports: - from: shared/constants.yaml use: variables: - SECRET # E536: 'SECRET' is not in source module's exports.variablesRelated
Section titled “Related”- Definitions —
$extendsinheritance,$withparameter binding,$paramsvalidation - Import System — packages, exports, and module resolution
- Error Codes — complete error code reference