AWS Step Functions with CDK: Define Workflows in Code Using .NET
Learn how to create AWS Step Functions using AWS CDK with .NET. We'll explore two approaches: loading existing ASL files directly into CDK, and using native CDK constructs to define workflows entirely in code.
AWS Cloud Development Kit (CDK) allows you to define cloud infrastructure using familiar programming languages instead of writing CloudFormation templates or using the AWS Console.
You can use CDK to create and manage AWS Step Functions workflows, giving you type safety, reusability, and version control for your workflow definitions.
In this post, we will learn:
- How to load existing ASL files into CDK
- How to use native CDK constructs to define workflows in code
- How to create AWS resources alongside your workflows
AWS CDK For The .NET Developer: How To Easily Get Started

This article is sponsored by AWS and is part of my Step Functions Series.
The Demo Project
The demo project is a CDK application created using the cdk init command.
The project contains multiple stacks to demonstrate different approaches to creating Step Functions.
StepFunctions/├── src/│ └── StepFunctions/│ ├── StepFunctionDemoStack.cs│ ├── UserOnboardingUsingASLStack.cs│ ├── UserOnboardingCDKStack.cs│ ├── UserOnboardingCDKWithResourcesStack.cs│ └── user-onboarding.asl.json└── cdk.json
We will walk through each stack, starting from loading existing ASL files to creating resources within CDK.
Approach 1: Loading Existing ASL Files in CDK
The most straightforward approach to using CDK with Step Functions is to load an existing ASL file and deploy it through CDK.
This is the ASL file we have been using in previous posts in this series, created using the Visual Workflow builder.
The UserOnboardingUsingASLStack.cs file contains the CDK stack definition to deploy the ASL file:
public class UserOnboardingUsingASLStack : Stack{public UserOnboardingUsingASLStack(Construct scope, string id, IStackProps props = null): base(scope, id, props){// Load ASL definition from filevar aslFilePath = Path.Combine(AppContext.BaseDirectory, "user-onboarding.asl.json");var aslDefinition = File.ReadAllText(aslFilePath);var stateMachineRole = Role.FromRoleArn( ... );// Create the state machine with the ASL definitionvar stateMachine = new StateMachine(this,"UserOnboardingCDKASL",new StateMachineProps{StateMachineName = "UserOnboardingCDKASL",DefinitionBody = DefinitionBody.FromString(aslDefinition),StateMachineType = StateMachineType.STANDARD,Role = stateMachineRole,});new CfnOutput( ... );}}
The code loads the ASL file as a string, references an existing IAM role, creates the StateMachine using DefinitionBody.FromString(), specifies the workflow type (STANDARD or EXPRESS), and outputs the state machine ARN.
Navigate to the StepFunctions folder and run cdk deploy UserOnboardingUsingASLStack
AWS CDK For The .NET Developer: How To Easily Get Started

CDK will synthesize the CloudFormation template and deploy it to AWS. Once complete, navigate to the AWS Console under Step Functions.
You'll see the new state machine UserOnboardingCDKASL deployed with the exact workflow definition from your ASL file.
This approach is useful when:
- You have already designed workflows using the visual builder
- You want to migrate existing ASL files to infrastructure-as-code
- You prefer designing in the AWS Console but deploying via CDK
However, this approach does not take advantage of CDK's native constructs and type safety.
Approach 2: Native CDK Constructs To Create AWS Step Functions
CDK provides first-class constructs to define workflows entirely in code, without requiring ASL files.
The StepFunctionDemoStack.cs demonstrates how to create a basic workflow using CDK constructs:
public class StepFunctionDemoStack : Stack{internal StepFunctionDemoStack(Construct scope, string id, IStackProps props = null): base(scope, id, props){// Pass state with JSONPath parametersvar passState = new Pass(this, "PassWithParameters", new PassProps{Comment = "Extract user from input using JSONPath",Assign = new Dictionary<string, object>{{"user.$", "$.user"},{"timestamp.$", "$$.State.EnteredTime"}}});// Wait statevar waitState = new Wait(this, "WaitState", new WaitProps{Comment = "Wait for 5 seconds",Time = WaitTime.Duration(Duration.Seconds(5))});// JSONATA pass statevar jsonataPass = new Pass(this, "JsonataPass", new PassProps{Comment = "Transform data with JSONATA",QueryLanguage = QueryLanguage.JSONATA,Outputs = new Dictionary<string, object>{{"Greeting", "{% 'Hello ' & $user.name %}"},{"Id", "{% $user.userId %}"}}});// Chain the states togethervar definition = passState.Next(waitState).Next(jsonataPass);new StateMachine(this, "CDKDemoStateMachine", new StateMachineProps{StateMachineName = "CDKDemoStateMachine",DefinitionBody = DefinitionBody.FromChainable(definition),StateMachineType = StateMachineType.STANDARD,});}}
Notice how each state is a native CDK construct:
- Pass - Creates a Pass state
- Wait - Creates a Wait state with duration
- Next() - Chains states together
The definition variable uses method chaining to build the workflow flow, then we pass it to DefinitionBody.FromChainable().
Deploy with cdk deploy StepFunctionDemoStack and navigate to the AWS Console to view the CDKDemoStateMachine.
The workflow contains three states (PassWithParameters, WaitState with 5-second delay, and JsonataPass) that can be tested with a user input to see the execution progress through each state before completing with a greeting message.
Extract Generated ASL after CDK synth
When you run cdk synth, CDK converts your C# constructs into ASL automatically.
Navigate to cdk.out/StepFunctionDemoStack.template.json and you will see the generated CloudFormation template with the ASL embedded in the DefinitionString property.
A PowerShell script, .\extract-asl.ps1 can extract these ASL definitions for easier visualization. This creates an asl-generated folder with the extracted ASL files, which can be visualized using the Visual Studio Code Step Functions extension.
User Onboarding Workflow with CDK Constructs
The user onboarding workflow from previous videos can be recreated using CDK constructs.
AWS Step Functions and AWS Service Integrations

The UserOnboardingCDKStack.cs file contains the complete workflow definition:
This recreates our entire user onboarding workflow using CDK constructs
- Pass state - Sets the
$uservariable - DynamoDB PutItem - Saves user to table
- Choice state - Checks for referral code
- Lambda Invoke - Processes referral (with retry and error handling)
- SQS SendMessage - Publishes UserOnboarded event
Notice how we can add retries and error handling directly in code:
lambdaInvoke.AddRetry(new RetryProps { ... });lambdaInvoke.AddCatch(passStateError, new CatchProps { ... });
Deploy it using cdk deploy UserOnboardingCDKStack
The workflow executes exactly like the ASL version built manually, but is now defined entirely in code.
Creating AWS Resources For Step Functions Within CDK
The previous examples referenced existing AWS resources (DynamoDB table, Lambda function, SQS queue).
Since we are already using CDK, these resources can be created in the same stack.
This makes the infrastructure completely self-contained and version-controlled.
The UserOnboardingCDKWithResourcesStack.cs file contains the complete stack definition with all resources:
public class UserOnboardingCDKWithResourcesStack : Stack{internal UserOnboardingCDKWithResourcesStack(Construct scope, string id, IStackProps props = null): base(scope, id, props){// Create DynamoDB Tablevar userTable = new Table(this, "UserTable", new TableProps{TableName = "User-OnboardingWorkflow",PartitionKey = new Attribute{Name = "PK",Type = AttributeType.STRING},...});// Create SQS Queuevar onboardingQueue = new Queue(this, "OnboardingQueue", new QueueProps{QueueName = "user-onboarding-workflow-queue",...});// Create Lambda Functionvar onboardingFunction = new Function(this, "OnboardingFunction", new FunctionProps{Runtime = Runtime.DOTNET_8,Handler = "user-onboarding-workflow::user_onboarding_workflow.Function::FunctionHandler",Code = Code.FromAsset("../user-onboarding-workflow", new AssetOptions{Bundling = new BundlingOptions { ... }}),FunctionName = "user-onboarding-workflow",...});// Create IAM role for state machinevar stateMachineRole = new Role(this, "StateMachineRole", new RoleProps{AssumedBy = new ServicePrincipal("states.amazonaws.com"),});// Grant permissionsuserTable.GrantReadWriteData(stateMachineRole);onboardingFunction.GrantInvoke(stateMachineRole);onboardingQueue.GrantSendMessages(stateMachineRole);// ... (rest of workflow definition same as before)}}
CDK creates all resources (DynamoDB table, SQS queue, Lambda function, IAM role) with proper permissions configured using built-in grant methods.
The Lambda bundling configuration automatically runs dotnet publish, packages the deployment asset, uploads to S3, and deploys the function during cdk deploy.
CDK vs ASL: When to Use Each Approach
Both approaches have their use cases.
Use ASL Files When:
- You prefer visual design with the AWS Console workflow builder
- You are migrating existing workflows to infrastructure-as-code
- Team members are not comfortable with CDK
- You want to quickly prototype workflows
Use Native CDK Constructs When:
- You want type safety and IntelliSense support
- You need reusable workflow components
- You want to generate workflows programmatically
- You are already managing other infrastructure with CDK
- You want to create resources alongside workflows
Combining Both Approaches
You can design workflows visually in the AWS Console, export the ASL, and use AI tools to convert it to CDK constructs.