Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
7.8 KiB
Windmill Flow Building Guide
OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
Reserved Module IDs
failure- Reserved for failure handler modulepreprocessor- Reserved for preprocessor moduleInput- Reserved for flow input reference
Hard Structural Rules
These are strict Windmill schema rules. Follow them exactly.
value.modulesis only for normal sequential stepsvalue.preprocessor_moduleandvalue.failure_moduleare special top-level fields insidevalue, not entries invalue.modules- If a flow needs a preprocessor, create
value.preprocessor_modulewithid: preprocessor - If a flow needs a failure handler, create
value.failure_modulewithid: failure - Do NOT create regular modules inside
value.modulesnamedpreprocessororfailure preprocessor_moduleandfailure_moduleonly supportscriptorrawscriptpreprocessor_moduleruns before normal modules and cannot referenceresults.*failure_modulecan use theerrorobject witherror.message,error.step_id,error.name, anderror.stack
Correct shape:
value:
preprocessor_module:
id: preprocessor
value:
type: rawscript
...
failure_module:
id: failure
value:
type: rawscript
...
modules:
- id: process_event
value:
type: rawscript
...
Incorrect shape:
value:
modules:
- id: preprocessor
...
- id: process_event
...
- id: failure
...
Module ID Rules
- Must be unique across the entire flow
- Use underscores, not spaces (e.g.,
fetch_datanotfetch data) - Use descriptive names that reflect the step's purpose
Common Mistakes to Avoid
- Missing
input_transforms- Rawscript parameters won't receive values without them - Referencing future steps -
results.step_idonly works for steps that execute before the current one - Duplicate module IDs - Each module ID must be unique in the flow
Data Flow Between Steps
flow_input.property- Access flow input parametersresults.step_id- Access output from a previous step only when that step result is in scoperesults.step_id.property- Access specific property from a previous step output only when that step result is in scopeflow_input.iter.value- Current iteration value when inside a loop (forloopfloworwhileloopflow)flow_input.iter.index- Current loop index when inside a loop (forloopfloworwhileloopflow)
Loop Structure Rules
- For
whileloopflow, use module-levelstop_after_ifon the loop module itself when the loop should stop after an iteration result - Do NOT put
stop_after_ifinsidevalueof awhileloopflow stop_after_all_iters_ifis for checks after the whole loop finishes, not the normal per-iteration break condition- When a
whileloopflowcarries state forward between iterations, useflow_input.iter.valueas the current loop value and provide an explicit first-iteration fallback when needed - Use
flow_input.iter.indexonly when the loop logic is truly based on the iteration index, not as a replacement for the current loop value - If the user asks for a final scalar/object after a loop, add a normal step after the loop that extracts the final value from the loop result instead of returning the whole loop result array
Correct whileloopflow shape:
- id: loop_until_done
stop_after_if:
expr: result.done === true
skip_if_stopped: false
value:
type: whileloopflow
skip_failures: false
modules:
- id: advance_state
value:
type: rawscript
input_transforms:
state:
type: javascript
expr: flow_input.iter && flow_input.iter.value !== undefined ? flow_input.iter.value : flow_input.initial_state
- id: return_final_state
value:
type: rawscript
input_transforms:
final_state:
type: javascript
expr: results.loop_until_done[results.loop_until_done.length - 1]
Incorrect whileloopflow patterns:
- id: loop_until_done
value:
type: whileloopflow
stop_after_if:
expr: result.done === true
input_transforms:
state:
type: javascript
expr: flow_input.iter.index
input_transforms:
final_state:
type: javascript
expr: results.loop_until_done
Approval / Suspend Structure
suspendbelongs on the flow module object itself, as a sibling ofidandvalue- Never put
suspendinsidevalue
Correct shape:
- id: request_approval
suspend:
required_events: 1
resume_form:
schema:
type: object
properties:
comment:
type: string
required: [comment]
value:
type: identity
Incorrect shape:
- id: request_approval
value:
type: rawscript
suspend:
required_events: 1
Branch Result Scope Rules
- Inside a branch, you may reference earlier outer steps and earlier steps in the same branch
- Outside a
branchone, do NOT reference ids of steps that only exist inside its branches or default branch. Useresults.<branchone_module_id>instead - Outside a
branchall, do NOT reference ids of steps inside its branches. Useresults.<branchall_module_id>instead - If downstream steps need a stable shape after a branch, make each branch return the same fields
- When needed, add a normalization step immediately after the branch and consume
results.<branch_module_id>there
Correct after branchone:
- id: route_order
value:
type: branchone
...
- id: send_confirmation
value:
input_transforms:
routed:
type: javascript
expr: results.route_order
Incorrect after branchone:
expr: results.create_shipment
expr: results.create_backorder
Correct after branchall:
- id: enrich_parallel
value:
type: branchall
parallel: true
...
- id: combine_data
value:
input_transforms:
enrichments:
type: javascript
expr: results.enrich_parallel
Input Transforms
Every rawscript module needs input_transforms to map function parameters to values:
Static transform (fixed value): {"param_name": {"type": "static", "value": "fixed_string"}}
JavaScript transform (dynamic expression): {"param_name": {"type": "javascript", "expr": "results.previous_step.data"}}
Resource References
- For flow inputs: Use type
"object"with format"resource-{type}"(e.g.,"resource-postgresql") - For step inputs: Use static value
"$res:path/to/resource"
Final Structural Self-Check
Before finalizing a flow, verify:
- any preprocessor is in
value.preprocessor_module - any failure handler is in
value.failure_module - any approval step has module-level
suspend - no downstream step references inner branch step ids from outside the branch
S3 Object Operations
Windmill provides built-in support for S3-compatible storage operations.
To accept an S3 object as flow input:
{
"type": "object",
"properties": {
"file": {
"type": "object",
"format": "resource-s3_object",
"description": "File to process"
}
}
}
Using Resources in Flows
On Windmill, credentials and configuration are stored in resources. Resource types define the format of the resource.
As Flow Input
In the flow schema, set the property type to "object" with format "resource-{type}":
{
"type": "object",
"properties": {
"database": {
"type": "object",
"format": "resource-postgresql",
"description": "Database connection"
}
}
}
As Step Input (Static Reference)
Reference a specific resource using $res: prefix:
{
"database": {
"type": "static",
"value": "$res:f/folder/my_database"
}
}