Public preview Grafana Cloud
Last reviewed: October 13, 2026

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:

YAML
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 $${:

YAML
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:

YAML
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:

YAML
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:

text
${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.

YAML
inputs:
  message: "Incident ${resources.incident.event.title} is ${resources.incident.event.status}"
  severity: "${resources.incident.event.severity}"

For alert groups:

YAML
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:

YAML
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:

text
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:

FunctionDescriptionExample
json_parse(string)Parse a JSON string into a structured objectjson_parse(steps['fetch-data'].outputs.responseBody).name
json_stringify(value)Convert a value to a JSON stringjson_stringify(inputs.data)
now()Return the current time in UTCstring(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:

text
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:

text
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:

YAML
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