Variable Expansion
LyftData jobs ship with two families of placeholders. Double curly braces ({{ }}) resolve before the job ever runs, while dollar curly braces (${ }}) resolve at runtime for each event. Using the right style in the right place keeps pipelines portable across environments.
Context placeholders ({{ }})
Context values render when you stage or deploy a job. Effective precedence (highest to lowest) is:
- Job overrides from the control plane (
/api/contexts/update-job-map/{job}). - Worker-level overrides (worker context).
- Global defaults.
- Job definition defaults in the job’s own
context:block.
Reference nested maps with dot notation ({{credentials.api_key}}). Missing keys cause staging to fail, so provide defaults in context files rather than relying on silent fallbacks. Use {{ }} for non-sensitive constants such as dataset names, feature toggles, and environment-specific endpoints. For secrets, prefer runtime lookups like ${secret|scope/name} (or ${dyn|NAME}) so you do not bake secret material into staged job YAML.
Runtime expansions (${ }})
Runtime expressions evaluate inside the worker after an event arrives. Many expansions support an optional || fallback suffix (for example ${field||default} or ${dyn|NAME||default}). Secret references (${secret|scope/name}) do not support defaults.
| Syntax | Source | Example |
|---|---|---|
${field} | Current event | `${client.ip |
| `${msg | path}` | Message payload |
| `${dyn | NAME}` | Variables |
| `${secret | scope/name}` | Secrets |
| `${cred | id | |
| `${stat | _BATCH_NUMBER}` | Output metadata |
| `${time | …}` | Scheduler clocks |
Event data
${field} reads values from the current event using dot-paths (${client.ip}) or slash-paths (${client/ip}). For strict JSON Pointer selectors, start with / (for example ${/client/ip}). Supply fallbacks for optional keys to keep streaming jobs healthy:
- add: output-fields: hostname: "${server||unknown-host}" disk_usage_pc: "${metrics.disk_usage_pc||0}"Message payloads
Jobs that run on a message trigger can read the triggering message’s public payload with ${msg|...} (see Advanced scheduling). The available fields depend on the message type.
If you use the internal-messages input instead of a message trigger, the message wrapper is delivered as the event. In that case, use normal event expansions (for example ${message_data.job_event...}) rather than ${msg|...}. See Message Bus.
For user-generated messages emitted by the message output, the original event is available under job_event:
- add: output-fields: upstream_order_id: "${msg|job_event.order_id||unknown}" upstream_tag: "${msg|tag||}"For Trigger invocations (from the Triggers registry), the dispatched payload is also a user-generated message. The invocation details are available under ${msg|job_event...}:
- add: output-fields: invocation_id: "${msg|job_event.invocation_id}" requested_by: "${msg|job_event.caller.username}" param_env: "${msg|job_event.params.env||}"Dynamic variables
${dyn|NAME} resolves values from the Variables service. If you reference a variable without a default, LyftData expects it to exist. Staging and transient runs reject missing required variables early; long-lived deployed jobs can stall until the variable is provided. Use || defaults for optional settings.
Batch metadata
Outputs that batch events expose two counters:
${stat|_BATCH_NUMBER}- zero-padded batch index.${stat|_SCHEMA_NUMBER}- schema revision when schema tracking is enabled.
Use these to build stable object keys:
output: s3: bucket-name: logs-{{environment}} object-name: name: "runs/${time|now_time_fmt %Y/%m/%d}/run-${stat|_BATCH_NUMBER}.json"Time macros and offsets
${time|...} expressions read from the scheduler window. Common variants include:
${time|now_time_secs}- current wall clock in epoch seconds.${time|start_time_iso}/${time|end_time_iso}- window bounds in ISO-8601.${time|now_time_fmt %Y-%m-%d}- customstrftimeformatting.
Append + 5m, - 1h, or any humantime duration to apply offsets. Invalid offsets fail the run, so stick to supported units (ms, s, m, h, d).
Putting it together
A typical production job mixes both placeholder families:
context: dataset: security bucket: logs-{{environment}}
input: http-poll: url: "https://api.example.com/{{dataset}}/events?since=${time|start_time_iso - 5m}"
actions: - add: output-fields: request_id: "${dyn|request_id}" source_host: "${hostname||unknown}"
output: s3: bucket-name: "{{bucket}}" object-name: name: "${stat|_BATCH_NUMBER}/${time|now_time_fmt %Y/%m/%d}/events.json"Tips for reliable templates
- Use
||fallbacks on${ }expressions fed by optional fields or third-party payloads. - Keep context files in source control so reviewers can verify how
{{ }}placeholders resolve across environments. - For long-lived deployed jobs,
${dyn|...}without a default can stall processing when a required variable is absent; monitor worker logs for “waiting for variable” warnings. - Batch counters reset when an output restarts. Treat them as per-run identifiers rather than global sequences.
For additional operators (math helpers, string casing, metadata keys), search this docs site for ${ examples and cross-check with the in-product DSL documentation (Docs → Job DSL).