Scripting
LyftData ships a Lua 5.3 runtime for the script action. Use it when you need to derive fields, normalize payloads, or branch on complex logic without shelling out to external tooling. Scripts run on each event the action receives, working in-place on the JSON document.
Prefer a typed action when it already expresses the transformation. Use a script for bounded job-level logic, not as unrestricted application hosting.
Runtime boundaries
Section titled “Runtime boundaries”The Lua action removes dynamic loaders such as require, dofile, and load.
It does not provide a general shell, package manager, or arbitrary network
client. Additional Lua helpers must be reviewed and staged with the job so
every selected worker receives the same artifact.
Scripts execute in the job’s event path. Expensive loops, large intermediate tables, state growth, or per-event cryptography can reduce throughput or delay the whole job. Test representative payload sizes and failure cases on the target worker topology; a successful Run & Trace sample is not a sustained performance result.
Treat scripts as reviewed deployment code:
- validate missing, null, oversized, and malformed fields;
- bound loops, table growth, and retained state;
- keep sensitive values out of source and logs;
- review every bundled helper when the job changes; and
- monitor script errors, latency, memory, and downstream delivery after deployment.
Core syntax
Section titled “Core syntax”- script: let: - total: amount * tax_rate - normalized_status: string.upper(status) set: - site: '{{worker}}' merge: overwrite condition: amount ~= nilletlistsfield: expressionpairs whose values are evaluated for every event.setassigns literal values. Context expansions such as{{job}}and{{now}}are available.mergecontrols how existing fields are handled:unless-exists(default) keeps the original value if the field already exists.overwritealways replaces the value.errorleaves the event untouched and records an attachment when a scripted field already exists.
conditionguards the entire action. When it evaluates tofalse, none of theletorsetexpressions run.
Field names must be valid Lua identifiers (start with a letter, contain letters, numbers, or _). Nested fields use dot notation (http.status), and arrays are 1-indexed (hosts[1]). The pseudo field _E exposes the entire event for cloning or inspection.
Runtime helpers
Section titled “Runtime helpers”The runtime preloads a catalog of host helpers before your script executes: counters and aggregation, conditionals, time, network matching, hashing, identifiers, base64, encryption (feature-gated), job metrics, and a per-job state store.
Every helper — with parameters, return types, and the corresponding JavaScript name where one exists — is documented in the Scripting functions reference. That page is generated from the runtime’s own function registry, so it cannot drift from what the product ships; this guide deliberately does not repeat the catalog.
Lua’s base libraries (math, string, table, base) are available. Sandbox safety removes require, dofile, load, and collectgarbage. Referencing a missing field raises an error by default; support can flip the runtime flag to downgrade these to null assignments during troubleshooting.
Note The older Hotrod pipelines exposed helpers such as
emit()andip2asn(). LyftData no longer ships those bindings—scripts work on the current event only. Use theexpand-eventsaction when you need to fan out arrays into multiple events.
Examples
Section titled “Examples”Derived fields and normalization
Section titled “Derived fields and normalization”name: normalize-ordersinput: text: | {"amount": 125.50, "currency": "usd", "customer": "ALICE"}actions: - script: let: - total_cents: round(amount * 100) - currency: string.upper(currency) - customer: string.lower(customer) - observed_at: sec_ms() merge: overwriteoutput: write: console# {"amount":125.5,"currency":"USD","customer":"alice","total_cents":12550,"observed_at":1720000000000}Rolling sums with conditional resets
Section titled “Rolling sums with conditional resets”- script: let: - batch_total: sum("orders", revenue, status == 'ok') - batch_seq: count() condition: revenue ~= nilWhen status stops equalling ok, the accumulator resets; the next matching event starts a fresh running sum.
State between events
Section titled “State between events”- script: let: - last_status: store_get('status', 'unknown') - status_changed: last_status ~= status - _ignored: store_set('status', status_changed, status)The helper returns the previous status while updating the cache only when it actually changed.
Guarded execution
Section titled “Guarded execution”- script: condition: condn(env == 'prod', true, env == 'staging', run_count() % 10 == 0, false) let: - census: map('count', input_event_count(), 'warnings', warning_count())Production runs on every event; staging only every tenth batch; everything else skips the action entirely.
Extending the environment
Section titled “Extending the environment”init.lua
Section titled “init.lua”If your job package includes an init.lua file, LyftData loads it before any script runs. Use it to declare shared functions:
-- init.luafunction every(n) return count() % n == 0end
function normalize_country(code) local normalized = string.upper(code or '') if normalized == 'UK' then return 'GB' end return normalizedend- script: let: - counter: count() - should_emit: every(5) - country: normalize_country(country) condition: should_emitBundle init.lua under the job’s files: section so workers download it alongside the spec. Scripts run inside the same interpreter, so keep helper names unique to avoid collisions.
Loading additional Lua modules
Section titled “Loading additional Lua modules”Set the load attribute to import another Lua file bundled with the job:
- script: load: lib/string_utils.lua let: - segments: split_path(url) - tenant: segments[2]The referenced file is read from the job package before the action executes. This gives you a place to stage larger helper libraries while keeping init.lua for global bootstrap code.
-- lib/string_utils.luafunction split_path(url) local segments = {} for segment in string.gmatch(url or '', "[^/]+") do table.insert(segments, segment) end return segmentsendLoad helpers like this alongside the job so every worker sees the same implementation.
Side-effect scripts with run
Section titled “Side-effect scripts with run”The run option executes a Lua expression for each event without mutating the payload. Use it for callbacks defined in init.lua or modules loaded via load:
- script: run: > if error_count() > 0 and run_job_errors() % 50 == 0 then store_set('error_alert_marker', true, tostring(run_job_errors())) return true end return falserun scripts can still access and modify globals, but because they bypass let/set, events flow through unchanged.
Troubleshooting tips
Section titled “Troubleshooting tips”- Missing fields or helpers raise runtime errors that appear in the job attachments. When debugging, operations can flip the “suppress script errors” toggle to coerce failures to
nullassignments. - Remember that Lua arrays start at 1. When you need zero-based math, subtract 1 explicitly.
- Use the
filterorassertactions when you need to drop or block events—scripts only modify the document, they do not control flow. - Keep cryptographic keys outside the spec. Use Secrets or Credential Manager through a supported component instead of placing keys in ordinary context values or script source.
With these helpers and boundaries, the script action can cover specialized
pipeline logic while remaining part of a reviewed job artifact.