StepLens — AWS Step Functions for VS CodeVisualize, navigate and lint AWS Step Functions definitions (ASL) directly in VS Code. Supports YAML (Serverless Framework), raw JSON, and JSONata query language.
FeaturesInteractive Graph PreviewOpen a live graph of your state machine with a single click on the ⊤ toolbar icon.
Node annotations
Real-time Linter30+ rules covering structural validity, JSONata/JSONPath field usage, distributed Map configuration, and state name constraints. Errors and warnings appear inline as you type, with hover tooltips explaining each issue. Structural rules
JSONata rules (when
|
| Rule | Severity | Description |
|---|---|---|
| J-1 | Error/Warn | Wrong fields for the active query language (Parameters↔Arguments, OutputPath↔Output, etc.) |
| J-2 | Error | Choice branch uses Variable in JSONata mode (use Condition) or vice-versa; Condition must be wrapped in {%…%} |
| J-3 | Error | Invalid {%…%} expression: empty, unclosed brace/bracket/paren/string, $eval(), trailing operator, JSONPath $. syntax inside JSONata |
| J-4 | Error | $states.result used in a state that has no result (only available in Task, Parallel, Map) |
| J-5 | Error | ResultSelector is JSONPath-only — not available in JSONata mode |
| J-6 | Error | TimeoutSecondsPath / HeartbeatSecondsPath are JSONPath-only |
| J-7 | Error | SecondsPath / TimestampPath in Wait state are JSONPath-only |
| J-8 | Error | States.* intrinsic functions (e.g. States.Format) used inside a {%…%} JSONata expression — these only work in JSONPath mode |
| J-9 | Error | $$. (Context Object JSONPath syntax) used in JSONata mode — use $states.context instead |
Usage
- Open any YAML or JSON file containing a Step Functions definition.
- The ⊤ icon appears in the editor toolbar — click it to open the graph.
- Lint errors appear as squiggly underlines; hover over them to read the message.
- The status bar shows
✓ StepLens(clean),⚠ N alertes, or✗ N erreurs.
Icon not visible? The toolbar icon may be hidden behind the
···overflow menu (three dots at the right of the editor title bar). Click···to find it, then right-click it to pin it to the toolbar.
Commands
You can also run StepLens commands from the Command Palette (⇧⌘P on macOS, Ctrl+Shift+P on Windows/Linux):
| Command | Keyboard shortcut | Description |
|---|---|---|
StepLens: Open Graph Preview |
⌘⌥G / Ctrl+Alt+G |
Open the visual graph for the active file |
StepLens: Lint Current File |
— | Run the linter manually |
Settings
| Setting | Default | Description |
|---|---|---|
steplens.autoDetect |
true |
Auto-detect Step Functions files by looking for a States key |
steplens.lintOnType |
true |
Run linter on every keystroke |
steplens.lintOnSave |
true |
Run linter on file save |
Supported formats
Raw ASL (JSON)
{
"Comment": "Document approval workflow",
"StartAt": "SubmitDocument",
"States": {
"SubmitDocument": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-west-1:123456789012:function:submit-doc",
"Next": "ReviewDocument"
},
"ReviewDocument": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-west-1:123456789012:function:review-doc",
"End": true
}
}
}
Raw ASL with JSONata
{
"Comment": "Order routing with JSONata",
"QueryLanguage": "JSONata",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-west-1:123456789012:function:validate-order",
"Arguments": {
"orderId": "{% $states.input.id %}",
"total": "{% $states.input.price * $states.input.qty %}"
},
"Assign": { "orderType": "{% $states.input.type %}" },
"Next": "RouteByType"
},
"RouteByType": {
"Type": "Choice",
"Choices": [
{ "Condition": "{% $orderType = 'express' %}", "Next": "ExpressShipping" },
{ "Condition": "{% $orderType = 'standard' %}", "Next": "StandardShipping" }
],
"Default": "FallbackRoute"
},
"ExpressShipping": { "Type": "Task", "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:express", "End": true },
"StandardShipping": { "Type": "Task", "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:standard", "End": true },
"FallbackRoute": { "Type": "Fail", "Error": "UnknownType", "Cause": "Unrecognised order type" }
}
}
Serverless Framework (YAML) — flat wrapper
definition:
Comment: Document approval workflow
StartAt: ReceiveDocument
States:
ReceiveDocument:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Next: WaitForReview
WaitForReview:
Type: Task
Resource: arn:aws:states:::lambda:invoke.waitForTaskToken
Parameters:
FunctionName: request-review
Payload:
taskToken.$: $$.Task.Token
HeartbeatSeconds: 3600
Catch:
- ErrorEquals: [States.HeartbeatTimeout]
Next: ReviewTimedOut
Next: CheckDecision
CheckDecision:
Type: Choice
Choices:
- Variable: $.decision
StringEquals: approved
Next: PublishDocument
- Variable: $.decision
StringEquals: rejected
Next: RejectDocument
Default: ReviewTimedOut
PublishDocument:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Next: NotifyAuthor
RejectDocument:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Next: NotifyAuthor
NotifyAuthor:
Type: Task
Resource: arn:aws:states:::lambda:invoke
End: true
ReviewTimedOut:
Type: Fail
Error: ReviewTimeout
Cause: No reviewer response within the deadline
Serverless Framework (YAML) — named wrapper
documentApproval:
name: ${self:service}-${opt:stage}-doc-approval
role: !GetAtt DocumentApprovalRole.Arn
definition:
Comment: Named Serverless Framework state machine
StartAt: ReceiveDocument
States:
ReceiveDocument:
Type: Task
Resource: arn:aws:states:::lambda:invoke
End: true
Parallel branches
definition:
Comment: Enrich a user record with two parallel lookups
StartAt: EnrichUser
States:
EnrichUser:
Type: Parallel
Branches:
- StartAt: FetchLocation
States:
FetchLocation:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Parameters: { FunctionName: fetch-location, Payload.$: $ }
End: true
- StartAt: FetchPreferences
States:
FetchPreferences:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Parameters: { FunctionName: fetch-prefs, Payload.$: $ }
End: true
Next: MergeResults
MergeResults:
Type: Task
Resource: arn:aws:states:::lambda:invoke
End: true
Tip: Double-click the
EnrichUsernode in the graph to explore each branch in a dedicated tab. The node label shows ‖2 (2 branches).
Map iterator
definition:
Comment: Process a batch of uploaded files
StartAt: ProcessFiles
States:
ProcessFiles:
Type: Map
ItemsPath: $.files
MaxConcurrency: 5
Iterator:
StartAt: TranscodeFile
States:
TranscodeFile:
Type: Task
Resource: arn:aws:states:::lambda:invoke
Parameters: { FunctionName: transcode, Payload.$: $ }
Retry:
- ErrorEquals: [States.TaskFailed]
IntervalSeconds: 5
MaxAttempts: 3
BackoffRate: 2
End: true
Next: SaveManifest
SaveManifest:
Type: Task
Resource: arn:aws:states:::lambda:invoke
End: true
The
ProcessFilesnode label shows ×5 (MaxConcurrency) andTranscodeFileshows ↺ (Retry configured). A Parallel state with 3 branches would show ‖3.
Graph legend
Nodes
| Style | State type |
|---|---|
| Blue rectangle | Task |
| Yellow diamond | Choice |
| Teal rectangle | Wait |
| Grey rectangle | Pass |
| Green circle | Succeed |
| Red circle | Fail |
| Purple dashed rectangle | Parallel / Map |
| Orange border | State at cursor position |
Edges
| Style | Meaning |
|---|---|
| Grey solid arrow | Next transition |
| Amber arrow | Choice branch / Default |
| Red dashed arrow | Catch (error handler) |
JSONata quick reference
When QueryLanguage: "JSONata" is set (globally or per-state):
| JSONPath field | JSONata equivalent |
|---|---|
Parameters |
Arguments |
OutputPath |
Output |
ItemsPath (Map) |
Items |
Variable (Choice) |
Condition |
JSONata expressions must be wrapped in {%…%}:
"Arguments": {
"name": "{% $states.input.user.name %}",
"total": "{% $states.input.price * $states.input.qty %}",
"id": "{% $uuid() %}"
}
$states variable paths:
| Path | Available in |
|---|---|
$states.input |
All states |
$states.result |
Task, Parallel, Map only |
$states.errorOutput |
Catch blocks only |
$states.context |
All states |
AWS-specific functions: $partition(), $range(), $hash(), $random(), $uuid(), $parse()
$eval()is not supported by AWS Step Functions.
License
MIT
