Conditional spintax: {?VAR?then|else}
Sometimes the choice between two phrasings is not a coin flip — it depends on a fact. The casino has crypto, or it doesn't. The product has a free tier, or it doesn't. Conditional spintax is the value-driven counterpart to {a|b} enum branches: instead of picking randomly, it picks based on whether a variable is truthy.
The three forms
Conditional syntax adds three new tokens on top of the GTW spintax family. All three are evaluated before enums and permutations resolve — they are a pre-pass, not a runtime branch.
| Form | Meaning |
|---|---|
{?VAR?then} |
Render then if %VAR% is truthy; render nothing otherwise. |
{?VAR?then|else} |
Render then if truthy; render else if falsy. |
{?!VAR?then[|else]} |
Inverted: render then when %VAR% is falsy. |
The ! prefix flips the check. There is no separate {?VAR??else} form — if you only want the falsy branch, write {?!VAR?else}.
Truthy and falsy
The truthy rule is deliberately simpler than JavaScript's. You should not have to remember coercion edge cases.
Value of %VAR% | Truthy? |
|---|---|
| not declared at all | falsy |
empty string "" | falsy |
| whitespace only (spaces, tabs, newlines) | falsy |
"0" | truthy (string zero is non-empty) |
"false" | truthy (still a non-empty string) |
"x", "<p>…</p>", raw spintax | truthy |
That is the whole rule. Truthy = at least one non-whitespace character. If you ever need a JavaScript-flavoured truthiness, do that check in the assembler before passing the variable in.
Truthiness reads the raw value
The lookup is on the raw stored value of the variable. Nested %var% references inside that value are not expanded for the truthy check — only later, when expandVariables runs as a separate stage.
#set %X% = %Other%
{?X?yes|no} → "yes"
Even if %Other% would expand to an empty string, %X%'s raw value is the literal string %Other% — which is non-empty, hence truthy. To get value-aware truthiness, write %X% as a clean '1' or '' directly. Compute the guard in the assembler before passing it in.
The two-pass pipeline
The engine processes a template in stages. Conditionals get two evaluation passes:
1. strip comments
2. extract #set directives
3. merge variables
4. apply conditionals ← pass 1
5. expand %var% references
6. apply conditionals ← pass 2
7. resolve enumerations
8. resolve permutations
9. post-process
Pass 1 handles conditionals authored directly in the template body. It runs before variable expansion so a falsy branch is discarded without wasting cycles on its %var%s.
Pass 2 handles the case where a variable's value itself contains a conditional:
#set %CTA% = {?HasBonus?Claim bonus|Deposit now}
%CTA% /# pass 2 sees the conditional after expansion #/
One clarification: pass 2 does not loop. If the chosen branch contains a fresh %var% reference, that reference is left literal — there is no third expansion pass. For most real templates this is fine; the guards used in conditional branches (%HasCrypto%, %HasFiat%) are typically the same ones already resolved in pass 1.
Worked example: banking blocks
This is the case the syntax was designed for. A casino review template renders a "Deposits and withdrawals" section. Some casinos accept crypto, some accept fiat, some accept both, a few accept neither.
The variable assembler computes two guards plus three pre-rendered HTML chunks:
%CasinoHasCrypto% "1" or ""
%CasinoHasFiat% "1" or ""
%CasinoCryptoSection% /# already-rendered <h3> + <ul> #/
%CasinoFiatSection%
%CasinoLimitsSection%
The orchestrator template uses conditionals to gate the optional fallback line for casinos with no payment data:
%CasinoFiatSection%%CasinoCryptoSection%%CasinoLimitsSection%
{?!CasinoHasCrypto?{?!CasinoHasFiat?<p>Banking details will be published shortly.</p>}}
Read the bottom line as: if not crypto and not fiat, render the fallback paragraph. Nested conditionals short-circuit outer-first, so when %CasinoHasCrypto% = "1" the outer falsy check fails immediately and the inner conditional is never evaluated.
Compare with the old workaround — a guard variable filled by spintax that picks between an empty string and a fallback paragraph at random with weighted probability. That worked, but the rendered output was non-deterministic and the template had to thread an extra variable through. Conditional syntax says exactly what it means.
Pick vs conditional — do not confuse them
Anti-patterns
1. Boolean operators
There is no &&, ||, !=, or == in conditional spintax. The syntax is intentionally minimal. If you need composite logic, compute the boolean in the variable assembler:
/# wrong: not supported #/
{?HasCrypto && HasLicense?…}
/# right: compose in the assembler #/
#set %ShowCryptoBlock% = {?HasCrypto?{?HasLicense?1}}
{?ShowCryptoBlock?…}
The assembler can use any host language and any logic. Spintax stays a templating tool, not a programming language.
2. #set inside a branch
#set directives are extracted before any conditional pass — that is step 2 of the pipeline. A #set line inside a {?…?…} branch fires unconditionally; the conditional only controls whether the empty leftover of the line stays in the chosen branch's text.
/# Both #set lines fire. The second wins. #/
{?A?
#set %x% = first
|}{?A?
#set %x% = second
|}%x%
→ always "second", regardless of A
If you need conditional assignment, do it in the assembler.
3. Top-level | as literal in then
The first depth-0 | inside the body separates then from else. Any further | at depth 0 stays literal — but in the else branch, not the then branch.
{?A?x|y|z} /# A truthy → "x"; A falsy → "y|z" #/
If the then branch needs a literal |, wrap it in nested braces or use the HTML entity |:
{?A?{x|y}} /# inner | is depth 1, not a separator #/
{?A?x | y} /# explicit entity, renders as "x | y" #/
4. Confusing pre-pass and runtime
Conditionals run as a pre-pass before enums and permutations resolve. That means a falsy branch is completely discarded — no random choice from it ever fires. If your template has a %RandomQuirk% permutation deep inside the falsy branch, that permutation is never evaluated when the conditional is falsy. Good. That is the point.
Lenient parsing — malformed forms are not fatal
Bare ? in prose ("How? Like this?") is common. The parser is deliberately lenient: any {?… that does not match the grammar is left literal rather than throwing.
The validator on this site (and the playground) does flag balanced-malformed shapes as warnings so you catch them in the editor:
| Form | Treatment |
|---|---|
{?VAR?then — no closing } | warning: unmatched open brace (same as any other {) |
{??yes} — empty name | warning: malformed conditional (empty name) |
{?VAR} — missing ? separator | warning: malformed conditional (missing separator) |
How? Like this? | plain prose, no warning |
At runtime, every malformed form is non-throwing. The engine continues into the normal enum/permutation passes; balanced malformed tokens may end up consumed by later stages, so do not rely on literal preservation.
Quick checklist
- Use
{?VAR?…}when the choice depends on a value, never{a|b}. - Use
{?!VAR?…}instead of inventing a separate "no" guard variable. - Compute composite booleans in the assembler. Spintax conditionals are atomic.
- Never put
#setinside a branch — it fires unconditionally. - If
thenneeds a literal|, wrap it in{…}or use|. - Truthy = non-whitespace, full stop. Pre-compute special truthiness in the assembler.
- Pre-render HTML fragments into variables for sections that change shape per tenant. Conditionals gate them.
Try it live
The playground ships with a {?HasFreeTier?…|…} example in the default template. Toggle %HasFreeTier% between 1 and empty to see both branches without typing anything else.