CDK Dependencies with CfnOutput and StackProps
Preface
In this post, we will cover how to manage cross-stack dependencies and resource sharing using the AWS Cloud Development Kit (CDK). We’ll explore two powerful features—CfnOutput and StackProps—and see how they help maintain clean, modular infrastructure code. Readers can expect a practical walk-through and actionable tips for structuring their AWS CDK applications.
Introduction
Imagine a fictitious fitness-tracking platform called RunTrackerPro, which collects workout data and stores it in multiple AWS services—an S3 bucket for raw data logs, a DynamoDB table for user metrics, and a couple of Lambda functions for data processing. To keep the infrastructure manageable, the team decides to split the resources into dedicated AWS CDK stacks. They want to reference resources between stacks without cluttering the code. For instance, the data-processing stack needs to read and write to the S3 bucket created in another stack, and the analytics stack needs the DynamoDB table name. Efficient cross-stack references ensure these stacks remain loosely coupled but still operate seamlessly.
This is where CfnOutput and StackProps come in. By leveraging these tools, RunTrackerPro ensures each stack only “knows” what it needs to know, making deployments easier and preventing integration headaches.
Why
Managing dependencies in larger AWS CDK projects is critical for:
- Orderly deployments: Avoiding circular dependencies by carefully exporting and importing only necessary resource information.
- Scalability: Splitting services into dedicated stacks to accommodate future growth, without tangling resources.
- Maintainability: Keeping each stack focused on a specific domain encourages a clearer code structure and faster development iterations.
Build Example
1. Creating the S3 Bucket Stack
Let’s set up a simple stack to create an Amazon S3 bucket. We’ll export its name using CfnOutput, so other stacks can consume it.
import { Stack, StackProps, aws_s3 as s3, CfnOutput } from "aws-cdk-lib";
import { Construct } from "constructs";
export class BucketStack extends Stack {
public readonly bucket: s3.Bucket;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
this.bucket = new s3.Bucket(this, "MyFitnessBucket", {
versioned: true,
});
new CfnOutput(this, "FitnessBucketNameExport", {
value: this.bucket.bucketName,
exportName: "FitnessBucketName",
});
}
}
Here, FitnessBucketNameExport
makes the bucket name visible to other stacks by creating a CloudFormation export.
2. Importing the Bucket Name in Another Stack
Next, we create a stack for data processing. It needs the bucket name to store processed logs. We’ll retrieve this using Fn.importValue to link it back to the bucket stack:
import { Stack, StackProps, Fn, aws_lambda as lambda } from "aws-cdk-lib";
import { Construct } from "constructs";
export class ProcessingStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const importedBucketName = Fn.importValue("FitnessBucketName");
const processDataLambda = new lambda.Function(this, "ProcessDataLambda", {
runtime: lambda.Runtime.NODEJS_18_X,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async (event) => {
console.log("Bucket name is: ${importedBucketName}");
return "OK";
};
`),
});
}
}
Using Fn.importValue("FitnessBucketName")
ensures that we import the correct bucket name exported by BucketStack
.
3. Passing References with StackProps
You might prefer passing entire constructs or other configuration details with StackProps. This approach is handy if you want direct access to an object—like a DynamoDB table instance—instead of just an exported string.
interface AnalyticsStackProps extends StackProps {
tableArn: string;
}
export class AnalyticsStack extends Stack {
constructor(scope: Construct, id: string, props: AnalyticsStackProps) {
super(scope, id, props);
// Use props.tableArn for further references
}
}
// In your app.ts or wherever you instantiate stacks:
const dynamoDBStack = new DynamoDBStack(app, "DynamoDBStack");
new AnalyticsStack(app, "AnalyticsStack", {
tableArn: dynamoDBStack.myTable.tableArn,
});
By embedding the DynamoDB table ARN directly into AnalyticsStackProps
, the receiving stack has all it needs to interact with the table, without needing to import from CloudFormation exports.
Gotchas
- ExportName conflicts: Make sure each exported resource has a unique name to avoid naming collisions.
- Deployment ordering: If
ProcessingStack
depends on outputs fromBucketStack
, ensureBucketStack
is deployed first or declared as a dependency, so references resolve correctly. - Environment consistency: You can only import exports within the same AWS account and region. Check environment configurations to avoid confusion.
Conclusion
By combining CfnOutput and StackProps effectively, you can maintain well-structured AWS CDK projects with clear cross-stack boundaries. Use CfnOutput to export critical details (like ARNs or resource names) and StackProps to pass entire constructs or properties when deeper interaction is needed. This modular approach leads to cleaner code, simpler deployments, and fewer surprises when your cloud infrastructure scales.
Actionable Takeaways:
- Use CfnOutput for inter-stack exports when you only need specific values (e.g., ARNs, names).
- Leverage StackProps when passing more complex objects or configurations between stacks.
- Avoid circular references by carefully planning your resource structure and deployment order.
- Test changes in a sandbox environment to confirm your exported imports work as expected before production deployment.
My Technical Skills

AWS

JavaScript

TypeScript

React

Next.js

Cypress

Figma
