---
title: "Use Terraform | Grafana Cloud documentation"
description: "Manage Grafana Workflows with the Grafana Terraform provider's generic app platform resource, including folders, variables, imports, and changes made outside Terraform."
---

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

# Use Terraform

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

You can manage Grafana Workflows with the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs). The provider doesn’t have a dedicated workflow resource. Instead, you use its general-purpose [`grafana_apps_generic_resource`](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/apps_generic_resource), which accepts a workflow definition as its `manifest`.

Terraform creates, updates, and deletes workflows. It doesn’t run them. To run a workflow or read its results, refer to [Use gcx](/docs/grafana-cloud/platform/workflows/as-code/use-gcx) or [Use the API](/docs/grafana-cloud/platform/workflows/as-code/use-the-api).

> Note
> 
> The Grafana Terraform provider marks `grafana_apps_generic_resource` as experimental, and the way it compares changes might change. Pin the provider version, and review each plan before you apply it.

## Before you begin

Before you start, make sure you have:

- [Terraform](https://developer.hashicorp.com/terraform/install) installed.
- A Grafana Cloud stack with the Workflows app installed.
- A service account token with the Editor basic role and edit access to the folders Terraform writes to. For details, refer to [Service accounts](/docs/grafana-cloud/security-and-account-management/authentication-and-permissions/service-accounts/) and [Manage access](/docs/grafana-cloud/platform/workflows/manage-access).

## Configure the provider

Add the Grafana provider to your Terraform files, and set `url` to your stack’s URL. The provider finds your stack’s namespace for you.

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

```hcl
terraform {
  required_providers {
    grafana = {
      source  = "grafana/grafana"
      version = ">= 4.46.0"
    }
  }
}

provider "grafana" {
  url = "https://<STACK_SLUG>.grafana.net/"
}
```

Keep the token out of your Terraform files. Instead, set it in the `GRAFANA_AUTH` environment variable before you run Terraform:

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

```bash
export GRAFANA_AUTH="<SERVICE_ACCOUNT_TOKEN>"
```

## Create a workflow from a YAML file

Keep each workflow in its own YAML file, and load the file into Terraform with the `yamldecode` function. To start from a workflow you built in the editor, copy its definition from **More** &gt; **View JSON**.

To create a workflow with Terraform, follow these steps:

1. Save the following definition as `workflows/incident-kickoff.yaml`, and replace *FOLDER\_UID* with your folder UID.
   
   The workflow runs when someone creates an incident. Its one step builds a summary.
   
   YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```yaml
   apiVersion: workflows.ext.grafana.app/v1alpha1
   kind: Definition
   metadata:
     name: incident-kickoff
     annotations:
       grafana.app/folder: <FOLDER_UID>
   spec:
     name: Incident kickoff
     enabled: true
     startWhen:
       matchingRules:
         - eventNameRegex: grafana_irm_app\.incident\.updated
           filter: "inputs.data.eventKind == 'incident_created'"
       schedules: []
     steps:
       - id: summary
         name: Build summary
         type: transform
         inputs:
           template: "New incident: ${resources.incident.event.title}"
   ```
   
   If you copied the file from the editor or the API, remove the fields that Grafana adds. These are `resourceVersion`, `uid`, `generation`, and `managedFields`.
2. Add a resource that loads the file:
   
   hcl ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```hcl
   resource "grafana_apps_generic_resource" "incident_kickoff" {
     manifest = yamldecode(file("${path.module}/workflows/incident-kickoff.yaml"))
   }
   ```
3. Preview the change:
   
   Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   terraform plan
   ```
   
   The plan shows one resource to add.
4. Create the workflow:
   
   Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   terraform apply
   ```
   
   If the file has a problem, the apply stops and names it, for example, `step "summary" is missing required field "template" for type "transform"`.
5. Confirm the workflow exists. Open the Workflows app, go to **Manage workflows**, and find **Incident kickoff**.

## Manage a folder of workflow files

To manage every YAML file in a folder with one resource block, use `for_each` with the `fileset` function:

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

```hcl
resource "grafana_apps_generic_resource" "workflow" {
  for_each = fileset("${path.module}/workflows", "*.yaml")

  manifest = yamldecode(file("${path.module}/workflows/${each.value}"))
}
```

When you add a YAML file, the next apply creates a workflow. When you remove a file, the next apply deletes its workflow.

## Create the Grafana folder with Terraform

Every workflow needs the `grafana.app/folder` annotation. If Terraform also manages the Grafana folder, use the folder’s UID from Terraform instead of typing it.

The following example creates a folder. It then uses the `merge` function to add the folder’s UID to your YAML file’s contents. With this pattern, the YAML file can leave out `annotations`.

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

```hcl
resource "grafana_folder" "automation" {
  title = "Incident automation"
}

locals {
  kickoff = yamldecode(file("${path.module}/workflows/incident-kickoff.yaml"))
}

resource "grafana_apps_generic_resource" "incident_kickoff" {
  manifest = merge(local.kickoff, {
    metadata = merge(local.kickoff.metadata, {
      annotations = {
        "grafana.app/folder" = grafana_folder.automation.uid
      }
    })
  })
}
```

## Write workflow expressions in Terraform files

You can also write the definition directly in HashiCorp Configuration Language (HCL), the language of Terraform files. Terraform and Workflows both use `${...}` for expressions. As a result, Terraform tries to fill in every Workflows expression in an HCL string.

To pass an expression through to Workflows, write it as `$${...}`. In the following example, `$${inputs.who}` reaches Workflows as `${inputs.who}`, and `var.enabled` is a Terraform variable:

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

```hcl
resource "grafana_apps_generic_resource" "hello" {
  manifest = {
    apiVersion = "workflows.ext.grafana.app/v1alpha1"
    kind       = "Definition"
    metadata = {
      name = "hello-workflow"
      annotations = {
        "grafana.app/folder" = grafana_folder.automation.uid
      }
    }
    spec = {
      name    = "Hello workflow"
      enabled = var.enabled
      startWhen = {
        matchingRules = []
        schedules     = []
      }
      steps = [
        {
          id   = "greet"
          name = "Build greeting"
          type = "transform"
          inputs = {
            template = "Hello $${inputs.who}"
          }
        }
      ]
    }
  }
}
```

Terraform doesn’t fill in expressions in files that you load with `file`, so YAML files need no changes. For workflows with many expressions, use YAML files.

## Reference secrets

A workflow definition refers to a secret by its address, such as `secret://...`, not by its value. You can safely keep these addresses in your Terraform files and in version control.

Create the secret values in the Workflows app or in Grafana Secrets Management. For details, refer to [Manage secrets](/docs/grafana-cloud/platform/workflows/manage-secrets).

## Import a workflow into Terraform

To manage a workflow that you built in the editor, *import* it into Terraform. You need the workflow’s ID, which is its `metadata.name`. The ID appears at the end of the editor URL.

To import a workflow, follow these steps:

1. Add a resource block whose manifest matches the workflow.
2. Import the workflow:
   
   Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   terraform import grafana_apps_generic_resource.incident_kickoff \
     workflows.ext.grafana.app/v1alpha1/Definition/incident-kickoff
   ```
   
   With Terraform 1.5 or later, you can use an `import` block instead:
   
   hcl ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```hcl
   import {
     to = grafana_apps_generic_resource.incident_kickoff
     id = "workflows.ext.grafana.app/v1alpha1/Definition/incident-kickoff"
   }
   ```
3. Preview the result:
   
   Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   terraform plan
   ```
   
   The first plan can show Terraform removing fields that Grafana adds, such as the `grafana.app/createdBy` annotation. These changes are safe to apply, and Grafana keeps its own fields. If the plan shows changes to `spec`, update your manifest until it matches the workflow.
4. Apply the result, then run `terraform plan` again.
   
   The plan shows no changes.

## Handle changes made outside Terraform

*Drift* is a change that someone makes to a workflow outside Terraform, for example, in the editor. When a workflow drifts, the next `terraform plan` shows the difference. The next `terraform apply` replaces the change with the version in your Terraform files.

The provider marks each workflow it creates with the `grafana.app/managedBy: terraform` annotation. The Workflows app doesn’t read this annotation, so it doesn’t stop edits in the editor. The provider’s `allow_ui_updates` argument doesn’t change this. Terraform overwrites those edits on the next apply.

Other tools do read the annotation. For example, gcx skips Terraform-managed workflows unless you add `--include-managed`.

To keep Terraform as the only way to change a workflow, follow these guidelines:

- Put Terraform-managed workflows in their own Grafana folder.
- Give most people view access to that folder. Give edit access only to the Terraform service account and the people who maintain it.
- Run `terraform plan` on a schedule to find drift early.

## Next steps

- [Use the API](/docs/grafana-cloud/platform/workflows/as-code/use-the-api)
- [Use gcx](/docs/grafana-cloud/platform/workflows/as-code/use-gcx)
- [Create workflows](/docs/grafana-cloud/platform/workflows/create-workflows)
