TGL: Recipes
TGL: Recipes
Real-world patterns. Each recipe is small, copy-pasteable, and combines features from the rest of the guide.
Filter test users
#if startsWith($row.user_id, "internal_")
$out = null
/if
$out = $row Drop incomplete records
#if isEmpty($row.email)
$out = null
/if
$out.email = lowerCase($row.email)
$out.name = $row.name isEmpty catches null, "", whitespace-only strings, [], and {} in one shot, so a customer with " " (a single space) for their email is filtered out the same as one with null.
Empty string to null
#if isEmpty($row["Type 2"])
$out.secondary_type = null
#else
$out.secondary_type = $row["Type 2"]
/if When a source field might be "" (especially with CSV ingestion, where empty cells parse as empty strings), use isEmpty to coerce blanks to null. Rollups treat null and "" as the same series, but downstream consumers (charts, exports, monitoring rules) read cleaner data when blanks are normalized at the transform layer.
Numeric coercion (CSV to math)
$out.line_total = multiply(float($row["Price"]), int($row["Quantity"])) Default values
$out.country = $row.address.country ?? "US"
$out.label = $row.label ?? "untitled"
$out.is_active = $row.is_active ?? true Sum line items
$out.total = 0
#each $row.line_items as $item
$out.total = add($out.total, multiply($item.price, $item.qty))
/each Build a tag array
$out.tags = null
#each $row.raw_tags as $tag
#if !isEmpty($tag)
$out.tags = push($out.tags, lowerCase(trim($tag)))
/if
/each Conditional output shape
$out.id = $row.id
$out.created_at = $row.timestamp
#if $row.type == "purchase"
$out.amount_cents = round(multiply($row.amount, 100))
$out.currency = $row.currency
#elseif $row.type == "refund"
$out.amount_cents = round(multiply(subtract(0, $row.amount), 100))
$out.refund_reason = $row.reason
#else
$out = null
/if Bucket by week
$out.week_start = toDateString(startOf(date($row.timestamp), "week"))
$out.event = $row.event
$out.user_id = $row.user_id Normalize an email
$out.email = lowerCase(trim($row.email))
$out.email_domain = split($out.email, "@")[1] Limits
TGL enforces hard limits to bound compilation and each record execution. Compile limits apply to the script as a whole. Runtime limits reset for every input record.
| Limit | Default | What happens when exceeded |
|---|---|---|
| Transform script request | 65,536 UTF-16 code units | Request validation error; the script is not compiled or stored |
| Statement count | 10,000 | Compile error; script rejected at storage time |
| Expression or control-flow depth | 200 levels | Parse or compile error; script rejected at storage time |
| Output path nesting | 50 levels | Compile error; script rejected at storage time |
| String length for one value | 10,485,760 UTF-16 code units | That value becomes null |
| Loop iterations per record | 1,000,000 | The record fails; processing continues with the next record |
| Output writes per record | 10,000 | The record fails; processing continues with the next record |
| Output array length | 10,000 | Oversized values become null; an out-of-range indexed write fails |
| Values and containers copied out | 100,000 | The record fails; processing continues with the next record |
| Cumulative output string/key bytes | 64 MiB | The record fails; processing continues with the next record |
Tailglow rejects a transform request longer than 65,536 code units. Other compile limits reject the script before it is saved. Runtime string and array limits can turn an individual value into null; iteration, write, and cumulative output limits skip the affected record.
Errors and validation
Storage-time validation
When you create or update a transform, Tailglow validates the script before saving it as active.
Scripts that fail compilation or output-field validation are not stored.
The output-field check compares the script’s statically known top-level writes with the view’s output schema. It is skipped for the exact mirror script $out = $row and for scripts with a dynamic top-level key such as $out[$row.key], because their field sets cannot be known at compile time. Other scripts are not stored until they pass both compilation and output-field validation.
Runtime errors
An invalid input record or a row that reaches a runtime safety limit is skipped while processing continues with the remaining records.
A structural output error, such as assigning an array to the root with $out = arrayValue, can mark a live transform as error or cause a backfill to fail after retries. Fix and save the script from the transform drawer.
Per-record skip
$out = null is not an error. It’s a normal filter signal. The record is dropped silently and the view continues.