Should you import a module or write a CDKTF construct?
There are three common approaches when implementing reusable infrastructure components:- Reuse a published Terraform module from the Terraform Registry.
- Import a local Terraform module (from another repo or a local path).
- Re-implement the logic as a CDKTF construct in TypeScript (or your chosen language) using provider bindings.
Tip: Start by importing modules to move quickly. When a component requires frequent changes or language-level abstractions, gradually rewrite it as a CDKTF construct.
Example: S3 bucket implemented as a CDKTF construct
Below is a concise CDKTF construct example that creates an S3 bucket and tags it with anenv tag. This TypeScript construct demonstrates a language-level abstraction you might prefer when you want a stable, reusable building block in your codebase.
- Re-implement that logic as a CDKTF construct (above),
- Import a published Terraform module from the registry, or
- Import a local Terraform module.
Import a local Terraform module using cdktf.json
To import a local module and have CDKTF generate bindings for it, add the module to yourcdktf.json using a relative or absolute source. Example:
cdktf.json:
.gen, allowing you to import and use the module like any construct.
Using the generated module from TypeScript
Once generated, import the module and use it in your stack similarly to native constructs:Decision guidance: import module vs write a construct
Use this table to choose the best approach for your component:Using CDKTF in an existing Terraform project (incremental migration)
You can migrate existing Terraform projects into CDKTF incrementally. Typical workflow:- Create a new folder and initialize a CDKTF project.
- Install the CDKTF CLI and run
cdktf init, choosing “Start from an existing Terraform project” when prompted.
The conversion tool is a strong starting point but not a full one-click migration. For larger projects, plan time to resolve generated import issues and to refactor modules into idiomatic CDKTF constructs where needed.
Example generated TypeScript (from conversion)
A convertedmain.ts may include generated provider and module bindings like this:
Design guidance: constructs and stacks
-
Constructs
- Encapsulate a single responsibility (e.g., “S3 bucket with logging” or “API with backend Lambda”).
- Keep public props minimal and stable.
- Group resources by deployment/ownership and dependency boundaries.
- Avoid embedding business logic that changes frequently into a low-level construct.
-
Stacks
- A stack should represent a deployable business unit (for example: API, frontend, database tier).
- Deploy stacks independently when you need separate lifecycle, teams, or scaling.
- Design stacks to match your deployment and ownership boundaries.
Further learning and resources
- CDKTF docs — Language-specific guidance and examples: https://developer.hashicorp.com/terraform/cdktf
- Terraform Registry — Reusable Terraform modules: https://registry.terraform.io/
- CDKTF community discussions and forum posts — practical patterns and troubleshooting

- CDKTF documentation (TypeScript examples included)
- Community forums and GitHub discussions for real-world patterns
- Build hands-on projects — practical experience is the best teacher
Summary
- Use imported Terraform modules to move quickly for stable, well-maintained components.
- Prefer CDKTF constructs when you need TypeScript-first abstractions or expect frequent changes.
- Use the CDKTF conversion tooling to speed migration from Terraform, then incrementally refactor generated code.
- Design constructs to have a single responsibility and design stacks around deployable business functionality.