TGL: Control flow
TGL: Control flow
TGL has three control-flow primitives: filtering a row out of the output, branching with #if, and looping with #each. All use Svelte-style directives (no curly braces).
Filtering: drop a row from the output
If you assign null to $out (the whole thing, not a field), the row is dropped from the output entirely:
// drop test users
#if startsWith($row.user_id, "internal_")
$out = null
/if
$out.user_id = $row.user_id
$out.event = $row.event When a row is dropped, no output record is emitted. The view simply has fewer records than the source.
This is the only way to filter rows. Everything else (writing to $out fields conditionally, etc.) still emits a record, just with different content.
A few notes on the rule:
- The skip is sticky: once
$out = nullruns, later writes have no effect. Use#if/#elseif you want to choose between drop and emit. - Expressions that evaluate to
nullalso drop. So$out = $row.payloaddrops the row wheneverpayloadisnull(and merges the payload into$outotherwise). - Other primitive assignments (
$out = "",$out = 42,$out = false) are no-ops, not drops. They leave$outunchanged. $out.field = nullwrites anullfield. It doesn’t drop the row.
Conditionals
#if / #elseif / #else / /if:
#if $row.score > 90
$out.grade = "A"
#elseif $row.score > 75
$out.grade = "B"
#elseif $row.score > 60
$out.grade = "C"
#else
$out.grade = "F"
/if The condition can be any expression. TGL uses standard truthiness: null, undefined, 0, "", false, and NaN are falsy; everything else is truthy.
Conditionals can be nested freely:
#if $row.user
#if !isEmpty($row.user.email)
$out.email = lowerCase($row.user.email)
/if
/if There’s no inline ternary (a ? b : c). Use #if/#else for branching values, or ?? for null-coalescing defaults (covered in Operators).
Loops
#each ... as $item / /each:
$out.total = 0
#each $row.line_items as $item
$out.total = add($out.total, multiply($item.price, $item.qty))
/each Note the accumulator pattern: the script initializes $out.total to 0, then the loop builds up the sum. add also treats a missing or null operand as 0, so $out.total = add($out.total, value) works even without the explicit initialization.
You can also bind an index:
#each $row.tags as $tag, $idx
$out.tags = push($out.tags, lowerCase($tag))
/each $tag is each element; $idx is the 0-based position.
If the collection isn’t an array (it’s null, an object, or a string), the loop body is silently skipped. That loop expression does not throw merely because it resolves to a different type.
There’s a hard cap on total loop iterations per record (the default is 1,000,000) to prevent runaway scripts. Hit it and the row produces an error and is skipped; the rest of the records continue processing normally.