Documentation for automated readers
A curated documentation index is available at: https://grafana.com/llms.txt
A complete documentation index is available at: https://grafana.com/llms-full.txt
These indexes can help with page discovery before fetching individual documents.
This page is also available in Markdown, which may be easier for automated readers and AI tools to parse than HTML. The Markdown version is available at https://grafana.com/docs/grafana-cloud/platform/workflows/use-expressions.md, or by sending Accept: text/markdown to https://grafana.com/docs/grafana-cloud/platform/workflows/use-expressions/. For broader documentation discovery, the curated index is available at https://grafana.com/llms.txt and the complete index is available at https://grafana.com/llms-full.txt.
Use CEL expressions
Note
Grafana Workflows is currently in public preview. Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
Grafana Workflows uses the Common Expression Language (CEL) for dynamic values, conditions, and data transformation. CEL expressions let you reference event data, access outputs from previous steps, write conditional logic, and build dynamic content throughout your workflow.
Understand the workflow context
The workflow context is the data that every step can read. It contains three top-level objects that you can reference in CEL expressions:
inputs: The event data that triggered the workflow.steps: The outputs from each step that has already run, keyed by step ID.resources: Loaded incident and alert group data, and references to your workflow secrets.
Each step can read the context, and its outputs become available to later steps.
Embed expressions in step inputs
Embed CEL expressions in step input values using ${expression} syntax:
inputs:
messageText: "Incident ${inputs.data.incidentID} has severity ${resources.incident.event.severity}"You can mix literal text with multiple expressions in a single field. Workflows evaluates each expression and replaces it with the result.
To include a literal ${ in the output, escape it with $${:
inputs:
template: "Use $${variable} for shell variables"Access event data
When an event triggers a workflow, the event payload is available through inputs.
The fields depend on the event source.
Fields from the event body are under inputs.data.
Incident and alert group events describe what changed and carry the ID of the incident or alert group.
They don’t carry the full object. For example, an incident event has inputs.data.incidentID and inputs.data.eventKind, but no severity.
To read fields such as the title or severity, use the loaded resource described in Access incident and alert group data.
For the fields each event includes, refer to Sample event payloads.
For example, to use the incident ID from a triggering event in an HTTP request:
inputs:
url: "https://api.example.com/incidents/${inputs.data.incidentID}"Access step outputs
Each step stores its outputs under steps.<STEP_ID>.outputs.
Later steps can reference these values to chain actions together.
The following example fetches data from an API and posts the response to Slack:
steps:
- id: fetch-data
type: http.call
inputs:
method: "GET"
url: "https://api.example.com/data"
- id: post-results
type: slack.message.post
inputs:
channelID: "C0123456789"
messageText: "API returned: ${steps['fetch-data'].outputs.responseBody}"When a step ID contains hyphens, use bracket notation:
${steps['fetch-data'].outputs.responseBody}For details on the outputs each step type produces, refer to Step and trigger types reference.
Access incident and alert group data
Workflows that trigger on incident events automatically load the full incident into the context at
resources.incident.event. Similarly, workflows triggered by alert group events automatically load the alert group at
resources.alertgroup.event. You can also declare additional resources in the resources.incidents or resources.alertgroups sections of the
definition.
inputs:
message: "Incident ${resources.incident.event.title} is ${resources.incident.event.status}"
severity: "${resources.incident.event.severity}"For alert groups:
inputs:
message: "Alert group ${resources.alertgroup.event.title} is ${resources.alertgroup.event.state}"
count: "${resources.alertgroup.event.alerts_count}"For resources you declare, use the reference name you defined:
inputs:
relatedTitle: "${resources.incident.related.title}"
alertGroupState: "${resources.alertgroup.current.state}"For details on declaring resources, refer to Load incident data and Load alert group data.
Write conditions
CEL supports standard comparison and logical operators for use in filter steps and switch branch conditions:
resources.incident.event.severity.matches('(?i)^critical$')
resources.alertgroup.event.alerts_count > 10
inputs.data.eventKind == "incident_updated_status" && inputs.data.newStatus == "resolved"
inputs.data.alertGroup.state == "firing" || inputs.data.alertGroup.state == "acknowledged"A filter step evaluates a condition and stops the workflow if the result is false.
A switch step evaluates branch conditions in order and runs the first matching branch.
For details on how to use filter and switch steps, refer to Create workflows.
Use built-in functions
Workflows extends CEL with custom functions for working with JSON data and the current time:
| Function | Description | Example |
|---|---|---|
json_parse(string) | Parse a JSON string into a structured object | json_parse(steps['fetch-data'].outputs.responseBody).name |
json_stringify(value) | Convert a value to a JSON string | json_stringify(inputs.data) |
now() | Return the current time in UTC | string(now()) |
Standard CEL string functions are also available, including contains, startsWith, endsWith, matches, and size.
Work with dates and times
Standard CEL date and time functions are also available, including timestamp(string) and duration(string). Use
them to compute a date relative to an existing timestamp, for example, ${timestamp(inputs.time) + duration('1h')}.
When you use a timestamp in a step input, Workflows writes it as a UTC RFC 3339 string, for example,
2026-06-11T11:00:00Z.
The now() function returns the current time as a CEL timestamp. Combine it with the standard CEL timestamp and duration functions to compare times, do arithmetic, and extract fields:
now() > timestamp("2026-01-01T00:00:00Z")
now() + duration("24h")
now().getFullYear()
now().getHours()now() is always in UTC. The string(now()) output ends with Z, and functions such as getHours() and getDayOfMonth() return UTC fields. To read a field in another timezone, pass an IANA timezone name or a fixed offset:
now().getHours("Europe/Madrid")
now().getHours("+02:00")now() returns the current time of the workflow run. The value advances as the workflow progresses, so you can measure elapsed time across steps. For example, a now() call after a wait step reflects the time that has passed:
steps:
- id: start
type: transform
inputs:
template: "${now()}"
- id: pause
type: wait
inputs:
seconds: "30"
- id: report
type: transform
inputs:
template: "Waited until ${now()}"Within a single evaluation the value is stable, so two now() calls in the same field return the same instant. In separate fields in the same step or in separate workflow steps, the value of now() can differ.
Handle type conversions
You can compare whole numbers and decimal numbers directly. For example, resources.alertgroup.event.alerts_count > 10 works without a type conversion.
Next steps
Was this page helpful?
Related resources from Grafana Labs


