Skip to content

Flows TF Provider

The Flows TF Provider can be used with OpenTofu and Terraform to manage Flows (yes, Flows-as-Code!), app installations, secrets, and more.

You can find installation and basic usage instructions in the GitHub repository and resource documentation on the OpenTofu registry.

When using with Terraform (as opposed to OpenTofu), you will have to provide the full registry path:

terraform {
required_providers {
flows = {
source = "registry.opentofu.org/spacelift-io/flows"
}
}
}

Whether you want to version control your flows, have a code-review driven change management process, deploy them across multiple environments, or generate them programmatically, the Flows TF Provider can help you achieve that.

Flows themselves have a YAML representation, which you can export from the UI. Without any blocks selected, click cmd+c to copy the entire flow (or with blocks selected, to copy just those), you can then paste that into a yaml file.

Here’s an example of such a Flow (the #1 sample flow):

blocks:
- name: Sample Endpoint
type:
coreBlock: httpEndpoint
position: [-50, 82]
config:
path: /hello/{name}
methods:
- GET
audience: world
inputs:
default:
links:
- block: Sleep
output: default
config:
statusCode: 200
body: !expr '`Hello, ${outputs.sampleEndpoint.params.name}`'
- name: Sleep
type:
coreBlock: sleep
position: [-50, 255]
inputs:
default:
links:
- block: Sample Endpoint
output: requests
config:
sleep: 5
notes:
- body: |-
# Simple Webhook Handler
You can see that the **HTTP Endpoint** block produces events on its `requests` output, and expects a response to arrive at its input.
To test this Flow, first click on the **Sample Endpoint** block, then click on the copy button next to the **Path** field. You can navigate to the copied URL using your browser, or curl it.
Once you do that and get a response, you will also see events appear on the canvas next to various outputs. Click on an output to see the events it produced!
You can **re-emit** those events, in order to replay parts of your flow - great for debugging, or just building up your flow piece by piece!
position: [273, 13]
style:
width: 400
height: 380
color: '#18181b'

This is pretty self-explanatory. For config fields, you can just input raw values, or provide expressions using the !expr tag.

It becomes slightly more complex if you have apps. Here you can see a Flow with app blocks used:

appInstallations:
slack:
appName: Slack
appVersion: 0.2.2
id: 019a4a1d-3494-758a-9df9-67d6e5f4660b
blocks:
- name: Mentions
type:
appBlock:
block: appMentionSubscription
appInstallation: slack
position: [116, 0]
- name: Is Hello Command
description: Checks if the mention contains a hello command
type:
coreBlock: condition
position: [0, 160]
inputs:
default:
links:
- block: Mentions
output: default
config:
condition: !expr /^<@U\w+>\s+hello$/i.test(outputs.mentions.text.trim())
- name: Hello Response
description: Sends a hello message to the user
type:
appBlock:
block: sendTextMessage
appInstallation: slack
position: [0, 293]
inputs:
default:
links:
- block: Is Hello Command
output: then
config:
channelId: !expr outputs.mentions.channel
text: !expr '`Hello <@${outputs.mentions.user}>!`'
threadTs: !expr outputs.mentions.thread_ts || outputs.mentions.ts

here you can see that the type of app blocks refers back to the installation they are related to, while an initial appInstallations section describes the apps used, their versions, and installation IDs.

Importantly, the yaml representation only manages the “configuration layer” of the Flow. It does not include e.g. lifecycle confirmation.

With the Flows TF Provider, you can use the flows_flow resource to create and update Flows. Additionally, you are able to parameterize the app installations (so you don’t have to include the IDs in the yaml file):

resource "flows_flow" "example" {
project_id = "your-project-id"
name = "my-flow"
definition = file("${path.module}/flow.yaml")
app_installation_mapping = {
my_app = "app-installation-id"
}
}

Moreover, if you have blocks with a lifecycle, you are able to confirm them using the flows_entity_lifecycle_confirmation:

resource "flows_entity_lifecycle_confirmation" "example" {
entity_id = flows_flow.example.blocks["my_entity"].id
}

Blocks are uniquely identified by their names. This means that if you just change a block name, it will be deleted and recreated - for most blocks this is fine, but you will lose e.g. the event history of that block. Additionally, it will be problematic for blocks that manage external resources (like AWS Resource blocks).

To rename a block without losing its identity, you can use the alternativeLookupStrategy field. Here’s an example of how to use it by referring to the old name of the block:

blocks:
- name: New Block Name
alternativeLookupStrategy:
name: Old Block Name
type:
appBlock:
block: appMentionSubscription
appInstallation: slack
position: [116, 0]

Alternatively, you can also directly refer to the block ID:

blocks:
- name: New Block Name
alternativeLookupStrategy:
id: 019be5ca-3832-717a-b5d7-dfe4bc19569a
type:
appBlock:
block: appMentionSubscription
appInstallation: slack
position: [116, 0]

See the Flow Definition Reference for complete YAML schema documentation.