---
title: "Use CEL expressions | Grafana Cloud documentation"
description: "Use Common Expression Language (CEL) expressions in Grafana Workflows to access event data, reference step outputs, write conditions, and transform data between steps."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Use CEL expressions

> Note
> 
> Grafana Workflows is currently in [public preview](/docs/release-life-cycle/). 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)](https://cel.dev/) 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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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](#access-incident-and-alert-group-data). For the fields each event includes, refer to [Sample event payloads](/docs/grafana-cloud/platform/workflows/reference/event-payloads).

For example, to use the incident ID from a triggering event in an HTTP request:

YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```text
${steps['fetch-data'].outputs.responseBody}
```

For details on the outputs each step type produces, refer to [Step and trigger types reference](/docs/grafana-cloud/platform/workflows/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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

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

For alert groups:

YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```yaml
inputs:
  relatedTitle: "${resources.incident.related.title}"
  alertGroupState: "${resources.alertgroup.current.state}"
```

For details on declaring resources, refer to [Load incident data](/docs/grafana-cloud/platform/workflows/load-incident-data) and [Load alert group data](/docs/grafana-cloud/platform/workflows/load-alert-group-data).

## Write conditions

CEL supports standard comparison and logical operators for use in `filter` steps and `switch` branch conditions:

text ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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](/docs/grafana-cloud/platform/workflows/create-workflows).

## Use built-in functions

Workflows extends CEL with custom functions for working with JSON data and the current time:

Expand table

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

text ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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 ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```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

- [Create workflows](/docs/grafana-cloud/platform/workflows/create-workflows)
- [Configure triggers](/docs/grafana-cloud/platform/workflows/configure-triggers)
- [Load incident data](/docs/grafana-cloud/platform/workflows/load-incident-data)
- [Load alert group data](/docs/grafana-cloud/platform/workflows/load-alert-group-data)
- [Manage secrets](/docs/grafana-cloud/platform/workflows/manage-secrets)
- [Sample event payloads](/docs/grafana-cloud/platform/workflows/reference/event-payloads)
- [Step and trigger types reference](/docs/grafana-cloud/platform/workflows/reference)
