Terraform
Manage bem functions and workflows declaratively with Terraform
The bem Terraform provider lets you declare functions and workflows as code, version them in your repo, and apply changes through your existing infrastructure pipeline. It calls the same V3 API as the SDKs and the dashboard, so anything you can build in the UI you can manage with Terraform.
Capabilities
The provider exposes the two primitives at the heart of bem and read-only data sources for everything you'd query.
| Resource | Purpose |
|---|---|
bem_function | Create and update functions of any type — extract, classify, split, join, enrich, payload_shaping, send. Owns the function's output schema, configuration, and tags. |
bem_workflow | Wire functions into a DAG. Owns nodes, edges, the entry-point node, connectors (e.g. Box, Dropbox, S3), and tags. |
| Data source | Returns |
|---|---|
bem_function | A single function by name. |
bem_functions | A list of functions in the environment. |
bem_workflow | A single workflow by name. |
bem_workflows | A list of workflows in the environment. |
Authentication uses an API key, picked up from the BEM_API_KEY environment variable or set explicitly in the provider block.
Functions and workflows are versioned on the bem side. Each terraform apply that changes a resource creates a new version — older versions remain readable and existing calls keep working.
Prerequisites
- A bem account and an API key generated from Settings → API Keys.
- Terraform CLI 1.0 or later.
Step 1: Set up and initialize the provider
Create a main.tf and declare the provider:
terraform {
required_providers {
bem = {
source = "bem-team/bem"
version = "~> 0.1"
}
}
}
provider "bem" {
# Reads BEM_API_KEY from the environment when omitted.
}Export your API key and initialize the working directory:
export BEM_API_KEY='your-api-key-here'
terraform initterraform init downloads the provider from the Terraform Registry and writes a lock file. You're ready to declare resources.
Step 2: Create a function and a workflow
Add an extract function and a single-node workflow that uses it. The output_schema is a standard JSON Schema, passed as a string via jsonencode so HCL stays readable:
resource "bem_function" "invoice_extractor" {
function_name = "invoice-extractor"
type = "extract"
display_name = "Invoice Extractor"
output_schema_name = "Invoice"
output_schema = jsonencode({
type = "object"
required = ["invoiceNumber", "vendor", "totalAmount"]
properties = {
invoiceNumber = { type = "string", description = "Unique invoice identifier" }
invoiceDate = { type = "string", description = "Invoice date (YYYY-MM-DD)" }
vendor = {
type = "object"
properties = {
name = { type = "string" }
address = { type = "string" }
}
}
totalAmount = { type = "number" }
}
})
tags = ["finance", "managed-by-terraform"]
}
resource "bem_workflow" "invoice_intake" {
name = "invoice-intake"
display_name = "Invoice Intake"
main_node_name = "extract"
nodes = [{
name = "extract"
function = {
name = bem_function.invoice_extractor.function_name
version_num = bem_function.invoice_extractor.function.version_num
}
}]
tags = ["finance"]
}The workflow references the function by name, and pins to its current version_num — Terraform will roll the workflow forward whenever the function version changes. Because there's only one node, edges can be omitted.
Apply the configuration:
terraform plan
terraform applyThe apply creates the function first, then the workflow that depends on it. Once it finishes, you can call the workflow exactly like any workflow created through the UI or SDKs.
Step 3: Update the function and workflow
Iterating is the same pattern as any Terraform resource: edit the configuration, plan, apply.
Suppose the schema needs a lineItems array and you want to add a payload-shaping step that reformats the output before delivery. Update the function and add a second node plus an edge to the workflow:
resource "bem_function" "invoice_extractor" {
function_name = "invoice-extractor"
type = "extract"
display_name = "Invoice Extractor"
output_schema_name = "Invoice"
output_schema = jsonencode({
type = "object"
required = ["invoiceNumber", "vendor", "totalAmount"]
properties = {
invoiceNumber = { type = "string", description = "Unique invoice identifier" }
invoiceDate = { type = "string", description = "Invoice date (YYYY-MM-DD)" }
vendor = {
type = "object"
properties = {
name = { type = "string" }
address = { type = "string" }
}
}
lineItems = {
type = "array"
items = {
type = "object"
properties = {
description = { type = "string" }
quantity = { type = "number" }
unitPrice = { type = "number" }
amount = { type = "number" }
}
}
}
totalAmount = { type = "number" }
}
})
tags = ["finance", "managed-by-terraform"]
}
resource "bem_function" "invoice_shaper" {
function_name = "invoice-shaper"
type = "payload_shaping"
display_name = "Invoice Shaper"
shaping_schema = jsonencode({
invoice_id = "invoiceNumber"
vendor = "vendor.name"
total = "totalAmount"
items = "lineItems"
})
}
resource "bem_workflow" "invoice_intake" {
name = "invoice-intake"
display_name = "Invoice Intake"
main_node_name = "extract"
nodes = [
{
name = "extract"
function = {
name = bem_function.invoice_extractor.function_name
version_num = bem_function.invoice_extractor.function.version_num
}
},
{
name = "shape"
function = {
name = bem_function.invoice_shaper.function_name
version_num = bem_function.invoice_shaper.function.version_num
}
},
]
edges = [{
source_node_name = "extract"
destination_node_name = "shape"
}]
tags = ["finance"]
}Plan and apply:
terraform plan
terraform applyTerraform issues an in-place update to the function (creating a new function version on bem's side) and updates the workflow to reference both functions and the new edge. Existing calls continue against their pinned version; new calls use the new one.
To remove the workflow, run terraform destroy — the workflow is deleted first, then the functions it referenced.