flow-as-code
Flow as code for Amazon Connect.
An Amazon Connect flow is a JSON document full of ARNs, and the ARNs differ per account, per region, per instance. So moving one flow from dev to prod means maintaining a mapping from "the queue I mean" to "the ARN it has in this environment", and something has to walk the flow and swap one for the other before deployment. That mapping table is the thing this tooling deletes.
Open the studio demo See it promote across environments Use it from Terraform Read the docs The demo needs a desktop-width screen. It is read-only, in your browser, no account and no network calls.
References are tokens
A flow refers to a queue by name, not by ARN:
new UpdateContactTargetQueue({
id: "set-working-queue",
queue: Refs.queue("appointments"),
next: "look-up-appointment",
onError: "apologize",
});
Refs.queue("appointments") becomes ${cdref:queue:appointments} in
the document. It stays a token through authoring, lint, version control and review. It
becomes an ARN at exactly one moment: when your IaC tool resolves it, per environment. Under
CDK the binding is a CloudFormation intrinsic, a Fn::GetAtt at a resource the
stack creates or an Fn::ImportValue of one it does not. Under Terraform it is a
templatefile variable your own resource addresses fill in.
That is a lint rule, not a convention: a literal ARN in authored content fails a hard rule, and in the studio a failing hard rule disables saving.
One flow, two environments
The promotion example is that argument as commands rather than prose: one FlowDoc reaching a dev and a prod environment, on both deploy paths. Two facts about it are worth stating because a test asserts each, on every push to main and every pull request.
-
Emitting the same document for dev and for prod produces two Terraform trees that differ
in exactly one file,
flow_refs.tf. The rendered flow template,flows.tfandvariables.tfare byte-identical, because the template names its variables after the tokens rather than after what they resolve to. No byte of either tree matchesarn:aws. -
The CDK path takes no map at all. Each stack's binder hands
FlowSeta value the app already holds: a construct attribute in the environment that creates its supporting resources, anFn.importValuein the environment where a platform team owns them. Neither synthesized template names a Connect ARN.
tests/promoteAcrossEnvironments.test.ts runs the example's own inputs and holds
both. The example's README also names what it does not show.
The map that is not that table
The Terraform path does take an --address-map, one small JSON file per
environment, and it looks like the table described above. It is a different object. Its
values are Terraform addresses of resources your own configuration manages,
aws_connect_queue.appointments.arn where the environment creates the queue and
data.terraform_remote_state.platform.outputs.appointments_queue_arn where a
platform team owns it. The emitter refuses any value matching /arn:aws/i
outright, telling you to map it to a terraform address instead, so the file cannot decay
into an ARN table.
The CDK path needs no such file because the flows and the resources are objects in one program, so the app already holds the reference and there is nothing to name. HCL has no equivalent, so something has to name the address in text. That, and only that, is why one path takes a map and the other takes none.
Or write the flow in Terraform
The flow-as-code/flowascode provider, on the Terraform and OpenTofu registries, takes
a flow as HCL action blocks and manages it through the Amazon Connect API. The actions name
what they use by key; one refs map says which queue that is in this
environment:
resource "flowascode_contact_flow" "support_line" {
instance_id = var.connect_instance_id
name = "support-line"
type = "CONTACT_FLOW"
refs = {
"queue:support" = aws_connect_queue.support.arn
}
action {
id = "set-queue"
next = "transfer"
update_contact_target_queue {
queue_id = "queue:support"
}
error {
type = "NoMatchingError"
next = "hang-up"
}
}
...
}
- The plan lints the flow before anything reaches Connect: a pasted ARN fails it, a missing error branch is flagged, and the branch rules match what Connect checks when it creates a flow.
- A change made in the Connect console shows in the next plan as a change to one action, not as a replaced blob of JSON.
- Promotion is one flows module applied by each environment's root module, and a pipeline can prove prod runs the document dev ran: its hash, with references still tokens, is known at plan time.
-
Existing flows are adopted with
importormovedblocks, not recreated, andflow-cli export --author tfwrites a whole instance as.flow.tffiles.
Your first flow with the provider,
promoting it from dev to prod,
bringing existing flows under Terraform, and
the flow cookbook walk through it
with output from a live instance. The studio edits a .flow.tf the way it edits
TypeScript, in both directions.
For AI coding agents
The repository ships Agent Skills that teach a coding
agent the rules Connect enforces, how to promote flows and how to adopt existing ones, with
a reference to every action type generated from the same catalog lint reads. In Claude Code:
/plugin marketplace add flow-as-code/flow-as-code. Any other agent can read
them as plain markdown from llms.txt.
What it is
Open-source tooling for Amazon Connect flows: typed flow authoring, lint, simulate-based testing, export from a live instance, and a visual editor, all round-tripping through one interchange format, with CDK and Terraform/OpenTofu code as outputs. Everything but the live commands works offline.
FlowDoc is the pivot. It is a JSON document holding Connect Flow-language content with
${cdref:type:name} reference tokens, plus layout and metadata. Every tool in
the set reads and writes it, so there is no second format to keep in sync.
typed TS builder --synth--> FlowDoc --codegen--> typed TS builder studio canvas <--edit--> FlowDoc FlowDoc <--codegen/synth--> .flow.tf (flowascode provider resource) FlowDoc --cdk--> CDK stacks (CloudFormation tokens) FlowDoc --tf--> .tf + .tftpl files (resource references) live instance --export--> FlowDoc (+ codegen to TS or HCL)
Packages
- @flow-as-code/core Typed builder, synthesizer, codegen, lint engine, FlowDoc interchange, export, and the simulate client.
- @flow-as-code/cli lint, codegen, synth, render, emit, studio, plus the live export and simulate.
-
@flow-as-code/studio
Visual editor over FlowDoc, served locally by
flow-cli studio. - @flow-as-code/cdk CDK token binding and the FlowSet construct.
- @flow-as-code/tf Terraform/OpenTofu emitter: FlowDoc to .tf and .tftpl files.
- @flow-as-code/hcl HCL as a third view: the .flow.tf companion, read and written losslessly.
- flow-as-code/flowascode The Terraform provider, on the Terraform and OpenTofu registries: flows as HCL action blocks, linted at plan time.
Try it
Published on npm under the @flow-as-code scope. The six packages version together and are released as a set, so install them at matching versions. Node 22.12 or newer.
npm i -D @flow-as-code/cli npx flow-cli init flows/ # a first flow to open: FlowDoc + .flow.ts npx flow-cli studio flows/ # visual editor, live-synced npx flow-cli lint flows/ # the rule set over the document set npx flow-cli emit flows/ --target tf # or --target cdk
Every command after the install works on a directory of FlowDocs, so init comes
first: it writes a small demo flow and the typed .flow.ts that synthesizes to
it. If nothing above the directory is already a package it adds a package.json marking the
pair as modules; inside a project that has one it leaves yours alone, and the pair loads
either way. Edit either side and the studio keeps them in step.
@flow-as-code/cli depends on the other five packages, so installing it alone is
enough for the CLI. For the Terraform provider, declare
flow-as-code/flowascode in required_providers; there is nothing to
install from npm. The studio demo above needs no install at all, and
the promotion example runs from a clone of
the repository.