Infrastructure as code (IaC) has become a standard way of deploying infrastructure to AWS Cloud. AWS Cloud Development Kit (AWS CDK) provides the means to enable developers to write the required cloud IaC in their favorite programming language, rather than writing declarative code in Yaml/JSON.

In this blog, we explore how you can leverage AWS CDK and how to harness the power of L1 and L2 constructs in AWS CDK stacks to create S3 buckets. But let’s start with defining these terms.

What is AWS CDK?

AWS CDK allows developers to define AWS Cloud infrastructure using familiar programming languages like C#, Python, TypeScript, and more. While it abstracts away many complexities, it also gives you granular control when you need it. With AWS CDK, you have the power to choose the level of abstraction that best suits your needs. Here’s how it works:


Note that being a high level language, C# requires the JSII runtime to translate the .NET types to the CDK runtime. CDK works on synthesizing the CDK application into a AWS CloudFormation template which subsequently gets deployed as a CloudFormation stack in the AWS account of your organization.


AWS CDK stack is a collection of resources known as ‘constructs’, where resources can be an AWS Service, or any component that helps in consuming an AWS Service defined in the Stack.


There can be multiple constructs defined in a stack and each stack can be represented by an instance of the stack, referenced directly or indirectly from a resource.

Defining L1 and L2 Constructs

A construct is part of a stack that defines the stack’s resources in detail. Put another way, L1 (Level 1) constructs enable low-level, granular control to define attributes for a stack’s resource. The construct contains all the details required to describe the cloud components in the metadata form (in JSON/Yaml) of the AWS CloudFormation template.

“A construct can represent a single AWS resource, such as an Amazon Simple Storage Service (Amazon S3) bucket. A construct can also be a higher-level abstraction consisting of multiple related AWS resources. Examples of such components include a worker queue with its associated compute capacity, or a scheduled job with monitoring resources and a dashboard.” *

The difference between an L1 & L2 Construct

The key to understanding the difference between a L1 and a L2 construct is “abstraction” and the level of abstraction provided.

An L1 construct is a low-level construct written in typescript, because Typescript sits nicely at a level that is most adjacent to the JSII runtime. An L2 construct is written in C#, for instance, for the CDK .NET framework which has an abstraction layer above the JSII runtime. 

This higher level of abstraction carries with it some constraints that are purely structural. This is because it necessitates an intermediary class that has to transform or ‘marshal’ the .NET types into the required JSII runtime type.

It is important to note that these constraints emerge due to the use of intermediary classes. These classes serve to convert .NET types into specific JSII runtime types, showcasing the detailed level of control required for AWS resource configurations.

When to Use L1 Over L2 Constructs

L2 constructs provide simplicity and ease of use, while L1 constructs give you finer-grained control over AWS resources. As mentioned earlier, because of the type constraints imposed by the .NET runtime, it becomes difficult for the JSII runtime to map such types, properties and / or methods with the fine granularity required by AWS CloudFormation. This is because CloudFormation stacks contain metadata that must fully resolve as per the best practices, security or other related aspects of the particular cloud component being described in the metadata.

Use Case: Assigning a User Friendly Name to S3 Bucket

S3 Bucket names are constrained to be unique but with some patterns. Hence, if by default bucket names contain a hash-like value to satisfy the uniqueness requirement it’s not very user-friendly. So, the obvious solution presents itself – that the bucket names must be customized while creating the S3 resource.

User-readable bucket name:


When we try to assign a name to an L2 level construct with the Bucket class that inherits from IBucket after it has been created, the compiler generates error msg “property or indexer Bucket.bucketname cannot be assigned to – it is read only”.

Solution: use the CfnBucket class:


When we try to assign a name to an L1 Bucket that inherits from ICfnBucket after it has been created, it works. This is because the L1 construct (all L1 constructs are prefixed with cfn, short for CloudFormation) is an indicator that the construct class maps directly with the CloudFormation template.

This applies to any AWS resources that may require mining into granular details that cannot be obtained with L2 constructs.

The Power of Choice and Control

This approach can be extended to other AWS resources, allowing you to achieve a balance between simplicity and control in your IaC projects.

The choice between L1 and L2 constructs depends on your project’s needs. L2 constructs are designed for simplicity and convenience, while L1 constructs offer more flexibility and control for intricate AWS resource configurations.

Connect with our AWS Experts to learn more about leveraging AWS CDK and constructs.