> ## Documentation Index
> Fetch the complete documentation index at: https://cdktn.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Constructs - CDK Terrain

> “Constructs are classes that allow you to reuse existing configurations. Learn about using constructs in your CDKTN application and how to publish custom constructs.”

## What is a construct?

In CDKTN, a construct is a building block that can represent anything from a single resource to a complex subset of your infrastructure. Each CDKTN program can be thought of as a tree being made up of constructs.

Since constructs can also represent a collection of resources, they are often compared with Terraform modules, but constructs provide a superset of what Terraform modules can do.

## Various use cases of constructs

* Terraform modules are limited in the sense that they can generate resources based on HCL configuration, but constructs are not constrained to just creating, and can also modify, enrich, and validate resources created outside of the construct.

* Constructs can build complex resources that can be used as any other provider-supplied resource without using a different syntax like in Terraform.

* Constructs allow strict type checking and parameter validation for creating resources. This is not possible in Terraform modules.

* Constructs can be used to codify best practices across the organization / codebase.

* Constructs can be tested like any other code within the CDKTN supported languages.

### Constructs can enrich and modify resource configuration to match best practices

While Terraform modules can be used to generate a part of infrastructure, constructs can be used to enrich and modify existing resource configurations. This enables authors to codify best practices within constructs, and users of those constructs can be assured that their infrastructure is compliant.

The following example extends the AWS S3 Bucket resource to add required tags. Any consumer of this `TaggedS3Bucket` can be assured that the correct tags are applied to their resource.

```ts theme={null}
class TaggedS3Bucket extends S3Bucket {
  constructor(scope: Construct, name: string, config: S3BucketConfig) {
    super(scope, name, config);

    this.tagsInput = {
      ...this.tagsInput,
      owner: "my-team",
      purpose: "storage",
    };
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new TaggedS3Bucket(this, "my-bucket", {
      bucket: "my-bucket",
    });
  }
}
```

Constructs are not limited to modifying specific resources and can be used to operate on entire stacks. The following example is a construct that can be used to list all S3 buckets in a stack and output them as a list.

```ts theme={null}
import { Construct } from "constructs";
class OutputAllBucketNames extends Construct {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    const buckets = [];
    Aspects.of(TerraformStack.of(this)).add({
      visit: (node) => {
        if (node instanceof S3Bucket) {
          buckets.push(node);
        }
      },
    });

    new TerraformOutput(this, "all_buckets", {
      value: new Lazy({
        produce: () => {
          return buckets;
        },
      }),
    });
  }
}
```

Constructs can also be used to mutate many existing resources after creation. In the following example, the `DataDogInspector` construct takes in a list of various constructs and whenever it encounters an `S3Bucket`, it enables logging to DataDog for that bucket. This is especially useful for applying team level compliance requirements that happen after the original infrastructure is created.

```ts theme={null}
import { Construct } from "constructs";
class DataDogInspector extends Construct {
  constructor(constructsToInspect: Construct[]) {
    super();
    constructsToInspect.forEach((c) => {
      if (c instanceof S3Bucket) {
        new DataDogS3Logging(c);
      }
    });
  }
}
```

### Constructs allow consumers of the construct to change behaviour conditionally

Since constructs are classes, they support declaring methods to programmatically add or modify infrastructure based on conditions. This enables the construct's consumer to selectively enhance their infrastructure.

In the following example, the `CustomS3Bucket` construct exposes a `giveAccess` method that can be used by the consumer to programmatically generate IAM policies for accessing the bucket for any resource that requires it.

```ts theme={null}
import { Construct } from "constructs";
class CustomS3Bucket extends S3Bucket {
  constructor(scope: Construct, name: string, config: S3BucketConfig) {
    super(scope, name);

    // create the s3 bucket
    // ...
  }

  public giveAccess(item: LambdaFunction | CloudfrontDistribution) {
    if (item instanceof LambdaFunction) {
      // Create IAM Policy for Lambda to access S3
      // ...
    }

    if (item instanceof CloudfrontDistribution) {
      // Create IAM Policy for CloudFront to access S3
      // ...
    }
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    const bucket = new CustomS3Bucket(this, "my-bucket", {
      bucket: "my-bucket",
    });

    const lambda = new LambdaFunction(this, "my-lambda", {
      // ...
    });

    const cloudfront = new CloudfrontDistribution(this, "my-cloudfront", {
      // ...
    });

    bucket.giveAccess(lambda);
    bucket.giveAccess(cloudfront);
  }
}
```

### Constructs can be used to codify relationships between systems

Constructs can also be used to represent relationships between different parts of the infrastructure in a type-safe manner. Creating relationships through constructs also has the benefit that the relationship is easily understandable in most modern IDEs.

In the following example, we have a contrived `Cluster` construct that allows various web services to be registered with it. During development, any consumer of the `Cluster` class will be able to see that any `Cluster` construct accepts `Webservice` constructs to be connected to it.

```ts theme={null}
import { Construct } from "constructs";

class Cluster extends Construct {
  constructor(scope: Construct, name: string) {
    super(scope, name);
  }

  addService(webservice: Webservice) {
    // e.g. register a service in a k8s cluster
  }
}

class Webservice extends Construct {
  constructor(scope: Construct, name: string) {
    super(scope, name);
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    const cluster = new Cluster(this, "cluster");
    const webservice = new Webservice(this, "webservice");

    cluster.addService(webservice);
  }
}
```

### You can codify architectural patterns

If you have complex architectural patterns you can codify them and have an abstraction where you can create a flexible architecture with less work. In this DataPipeline example you can see that each of the steps in the pipeline is bringing their own logic on how to connect to the next step and how to write their output into an S3Bucket. This means as long as a set of resources can fulfill this contract one could build a pipeline using e.g. LambdaFunctions, EC2 Instances, and CloudwatchEvents if one implemented the `PipelineStep` class for these types.

```ts theme={null}
abstract class PipelineStep extends Construct {
  abstract connect(next: PipelineStep);
  abstract finalize(sink: S3Bucket);
}

class DataPipeline extends Construct {
  _nextStep: PipelineStep;

  constructor(scope: Construct, name: string) {
    super(scope, name);
    // this is always the first step
    this._nextStep = new PipelineWebhookStep(this);
  }

  public addStep(step: PipelineStep) {
    this._nextStep.connect(step);
    this.addAccessPermissions(this._nextStep, step);
    this._nextStep = step;
  }

  public finalize() {
    this._nextStep.finalize();
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);
    const pipeline = new DataPipeline(this, "my-pipeline");
    pipeline.addStep(new TransformInputPipelineStep(this));
    pipeline.addStep(new AggregateValuesPipelineStep(this));
    pipeline.addStep(new PipelineStoreResultStep(this));
    pipeline.finalize();
  }
}
```

### You can codify security constraints earlier in the process

You can shift left security concerns for developers from the end of the workflow (like code review, CI, or solutions like Sentinel policies) to the point where the configuration is authored. This can be done through type hints (disallowing options that are not safe on the type system level) or through validation. Validations allow dynamic verification and are shown to users as hints during cdktn synth. If they are set to the error level, they also fail the synth.

#### Through Types

```ts theme={null}
import { Construct } from "constructs";
import { Ec2InstanceConfig as GeneratedEc2InstanceConfig } from "./.gen/providers/aws/lib/ec2-instance";

enum AllowedInstanceSizes {
  Nano = "t3.nano"
  Micro = "t3.micro"
  Small = "t3.small"
  Medium = "t3.medium"
}
interface Ec2InstanceConfig extends GeneratedEc2InstanceConfig {
  instanceSize: AllowedInstanceSizes;
}

class Ec2Instance extends Construct {
  constructor(scope: Construct, name: string, config: Ec2InstanceConfig) {
    super(scope, name, config);
  }
}
```

#### Through Validations

```ts theme={null}
import { Annotations, Construct } from "constructs";

class S3Bucket extends Construct {
  constructor(scope: Construct, name: string, config: S3BucketConfig) {
    super(scope, name);

    if (
      this.isOpenToThePublic(config) &&
      !config.tags["reason-why-this-is-public"]
    ) {
      Annotations.of(this).addWarning(
        `This S3Bucket is open to the public and no reason why is stated in the tags, please add a "reason-why-this-is-public" tag`,
      );
    }
  }
}
```

If your construct relies on a Terraform or OpenTofu feature that only exists from a certain version, you can also validate it declaratively against the project's [declared `targetVersions`](/create-and-deploy/configuration-file#declare-target-runtimes) rather than probing the installed binary. See [Validate Runtime Feature Availability](/develop-custom-constructs/construct-design#validate-runtime-feature-availability) for details.

### Constructs and Testing

Since Constructs can contain logic and are not just a data structure, they can be tested. This allows you to test the composition of your constructs and the logic they contain. This can be done with snapshot testing or unit testing.

```ts theme={null}
import { Testing } from "cdktn";
import MyApplicationsAbstraction from "../main";
import { Ec2 } from "./custom-ec2-construct";


describe("Construct is of correct composition and plans successfully", () => {
    // make assertions about the composition of constructs
    it("has EC2 of correct size", () => {
        expect(
          Testing.synthScope((scope) => {
            new MyApplicationsAbstraction(scope, "app", {});
          })
        ).toHaveResourceWithProperties(Ec2, {
          instanceSize: "t3.micro",
        });
    });

  // assert that plan runs sucessfully before a deploy
  it("successfully plans", () => {
    const app = Testing.app();
    const stack = new TerraformStack(app, "test");
    const myAppAbstraction = new MyApplicationsAbstraction(stack, "app", {});

    expect(Testing.fullSynth(stack)).toPlanSuccessfully();
  });

  // catch unexpected changes with snapshot testing
  it("matches snapshot", () => {
    expect(
        Testing.synthScope((scope) => {
            new MyApplicationsAbstraction(scope, "app", {});
        })
    ).toMatchSnapshot();
});
```

### Interoperability

You can use the [`cdktn convert`](/cli-reference/commands#convert) command to translate existing projects written in HashiCorp Configuration Language (HCL) into CDKTN-compatible projects. You could then use the output as a starting point to create custom constructs.

You can use the [`cdktn get`](/cli-reference/commands#get) command to download Terraform modules and use them as constructs in your CDKTN project. You can also [publish these generated constructs](/develop-custom-constructs/publishing-and-distribution#translate-constructs-to-terraform-modules) representing a module to your registry of choice.

You cannot create a Terraform module directly from an existing CDKTN construct, but you can use the synthesized output of a CDKTN project as a Terraform Module. Refer to the [HCL interoperability](/concepts/hcl-interoperability) page for more details.

## Constructs vs. Stacks

[Stacks](/concepts/stacks) represent a collection of infrastructure that CDK Terrain (CDKTN) synthesizes as a dedicated Terraform configuration. Stacks allow you to separate the state management for multiple environments within an application. For example, you may want to have one stack that describes the infrastructure for development and another with slightly different inputs for testing.

Constructs also provide a way to logically structure a set of resources, but you can only use them as part of a stack. A single stack may contain multiple constructs that act as building blocks to create the full infrastructure configuration. For example, you could use one construct to define a Kubernetes deployment and a different construct to define an AWS DynamoDB table.

## Construct Types

Construct classes define system state at various levels of granularity. For example, you can write a custom construct that defines and configures a single Elastic Cloud Compute resource or one that defines and configures an entire deployment with multiple resources from different providers.

The Cloud Development Kit community has identified three major construct types that indicate an increasing level of abstraction:

* **L1** constructs define single resources or very small units of configuration. For example, the code bindings that CDKTN generates for each Terraform provider are considered L1 constructs. Another example would be creating a custom L1 construct that defines the configuration for an Azure virtual machine.

* **L2** constructs define resources and include an intent-driven API with additional helper methods, properties, and functionality. For example, you could create a custom L2 construct that contains a method for adding files to an S3 Bucket and a method for granting resource access to a particular user group.

* **L3** constructs define common design patterns and larger pieces of functionality. For example, you could create a custom L3 construct that configures all of the necessary resources to deploy and host a static website frontend.

## Use Constructs

<Tip>Try [Deploy Applications with CDK Terrain](/tutorials/deploy-applications) tutorial to use a custom construct. It includes the example code below.</Tip>

You can import any [CDKTN-compatible](#available-constructs) construct that is available in your project's programming language. Then, you can create new instances of the construct and use any exposed properties to customize the construct configuration.

The following example instantiates a construct called `KubernetesWebAppDeployment` and uses the available arguments to specify that the deployment will have two replicas.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Construct } from "constructs";
  import { TerraformStack } from "cdktn";
  import { KubernetesWebAppDeployment } from "./constructs/kubernetes-web-app-deployment";
  import { KubernetesProvider } from "./.gen/providers/kubernetes/provider";
  import * as path from "path";

  export class ConstructsStack extends TerraformStack {
    constructor(scope: Construct, name: string) {
      super(scope, name);

      new KubernetesProvider(this, "kind", {
        configPath: path.join(__dirname, "../kubeconfig.yaml"),
      });

      new KubernetesWebAppDeployment(this, "deployment", {
        image: "nginx:latest",
        replicas: 2,
        app: "myapp",
        component: "frontend",
        environment: "dev",
      });
    }
  }
  ```

  ```python Python theme={null}
  class MyKubernetesStack(TerraformStack):
      def __init__(self, scope: Construct, name: str):
          super().__init__(scope, name)
          KubernetesProvider(self, "kind",
              config_path=os.path.join(os.path.dirname(__file__), '..', 'kubeconfig.yaml')
          )

          KubernetesWebAppDeployment(self, "deployment",
              image="nginx:latest",
              replicas=2,
              app="myapp",
              component="frontend",
              environment="dev"
          )



  app = App()
  MyKubernetesStack(app, "demo")
  app.synth()
  ```

  ```java Java theme={null}
  import java.nio.file.Paths;
  import imports.kubernetes.provider.KubernetesProvider;
  import imports.kubernetes.provider.KubernetesProviderConfig;
  import com.mycompany.app.myconstructs.KubernetesWebAppDeployment;
  import com.mycompany.app.myconstructs.KubernetesWebAppDeploymentConfig;


  public class MainUseConstructs extends TerraformStack {

      public MainUseConstructs(Construct scope, String name){
          super(scope, name);

          new KubernetesProvider(this, "kind", KubernetesProviderConfig.builder()
                  .configPath(Paths.get(System.getProperty("user.dir"), "..", "kubeconfig.yaml").toString())
                  .build()
          );

          new KubernetesWebAppDeployment(this, "deployment",  KubernetesWebAppDeploymentConfig.builder()
                  .image("nginx:latest")
                  .replicas(2)
                  .app("myapp")
                  .components("frontend")
                  .environments("dev")
                  .build()
          );
      }

      public static void main(String[] args) {
          final App app = new App();
          new MainUseConstructs(app, "demo");
          app.synth();
      }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Linq;
  using Constructs;
  using Io.Cdktn;
  using kubernetes.Provider;
  using MyConstructs;

  namespace Examples
  {
      class MyConstructsStack : TerraformStack
      {
          public MyConstructsStack(Construct scope, string name) : base(scope, name)
          {
              new KubernetesProvider(this, "kind", new KubernetesProviderConfig
              {
                  ConfigPath = Path.Join(Environment.CurrentDirectory, "../kubeconfig.yaml")
              });
              new KubernetesWebAppDeployment(this, "deployment", new Dictionary<string, object> {
                  { "image", "nginx:latest" },
                  { "replicas", 2 },
                  { "app", "myapp" },
                  { "component", "frontend" },
                  { "environment", "dev" }
              });
          }
      }
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  	kubernetes "github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/kubernetes/provider"
  	"github.com/open-constructs/cdk-terrain/examples/go/documentation/myconstructs"

  	"os"
  	"path"
  )

  func NewConstructsStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	cwd, _ := os.Getwd()

  	kubernetes.NewKubernetesProvider(stack, jsii.String("kind"), &kubernetes.KubernetesProviderConfig{
  		ConfigPath: jsii.String(path.Join(cwd, "kubeconfig.yaml")),
  	})
  	myconstructs.NewKubernetesWebAppDeployment(stack, "deployment", map[string]interface{}{
  		"image":       jsii.String("nginx:latest"),
  		"replicas":    jsii.Number(2),
  		"app":         jsii.String("myapp"),
  		"component":   jsii.String("frontend"),
  		"environment": jsii.String("dev"),
  	})

  	return stack
  }

  func main() {
  	app := cdktn.NewApp(nil)

  	NewConstructsStack(app, "constructs")

  	app.Synth()
  }

  ```
</CodeGroup>

### Scope

You can instantiate a construct multiple times throughout your infrastructure. For example, you may want to create multiple S3 Buckets with different configurations. CDKTN infers a unique `name` for each instance from its `Construct#id` and parent construct. Instances that share the same parent element are considered to be part of the same scope. If you instantiate multiple constructs within the same scope, you must set a different `name` for each instance to avoid naming conflicts.

The following example creates three different S3 buckets, two of which are in the same scope. When CDKTN synthesizes this configuration, the Terraform IDs for these resources will be the construct names prefixed by the stack name and suffixed with a hash for each Construct instance.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
  import { S3BucketWebsiteConfiguration } from "./.gen/providers/aws/s3-bucket-website-configuration";
  import { AwsProvider } from "./.gen/providers/aws/provider";
  import { Construct } from "constructs";
  import { TerraformStack } from "cdktn";

  export class PublicS3Bucket extends Construct {
    public bucket: S3Bucket;
    constructor(scope: Construct, name: string) {
      super(scope, name); // This creates a new scope since we extend from construct

      // This bucket is in a different scope than the buckets
      // defined in `MyStack`. Therefore, it does not need a unique name.
      this.bucket = new S3Bucket(this, "bucket", {
        bucketPrefix: name,
      });

      new S3BucketWebsiteConfiguration(this, "bucket-website", {
        bucket: this.bucket.bucket,
        indexDocument: {
          suffix: "index.html",
        },
        errorDocument: {
          key: "5xx.html",
        },
      });
    }
  }

  export class ConstructsScopeStack extends TerraformStack {
    constructor(scope: Construct, name: string) {
      super(scope, name);

      new AwsProvider(this, "aws", {
        region: "us-west-2",
      });

      // Both buckets are inside of the same stack, meaning they share
      // the same scope. Therefore, their names must be unique.
      new PublicS3Bucket(this, "first-bucket");
      new PublicS3Bucket(this, "second-bucket");
    }
  }
  ```

  ```python Python theme={null}
  from constructs import Construct
  from cdktn import App, TerraformStack

  class PublicS3Bucket(Construct):

      bucket: S3Bucket

      def __init__(self, scope: Construct, name: str):
          # This creates a new scope since we extend from construct
          super().__init__(scope, name)

          AwsProvider(self, "aws",
              region="us-east-1"
          )

          # This bucket is in a different scope than the buckets
          # defined in `MyStack`. Therefore, it does not need a unique name.
          self.bucket = S3Bucket(self, "bucket",
                          bucket_prefix=name,
                          website=S3BucketWebsite(
                              index_document="index.html",
                              error_document="5xx.html",
                          )
                      )

  class MyS3BucketStack(TerraformStack):
      def __init__(self, scope: Construct, name: str):
          super().__init__(scope, name)

          # Both buckets are inside of the same stack, meaning they share
          # the same scope. Therefore, their names must be unique.
          PublicS3Bucket(self, "first-bucket")
          PublicS3Bucket(self, "second-bucket")

  app = App()
  MyS3BucketStack(app, "s3-stack")
  app.synth()
  ```

  ```java Java theme={null}
  import software.constructs.Construct;
  import io.cdktn.cdktn.TerraformStack;
  import io.cdktn.cdktn.App;
  public class MainConstructScope extends TerraformStack {

      public MainConstructScope(Construct scope, String name){
          super(scope, name);

          new MainConstructScope.PublicS3Bucket(this, "first-bucket");
          new MainConstructScope.PublicS3Bucket(this, "second-bucket");

      }

      static class PublicS3Bucket extends Construct{

          public S3Bucket bucket;

          public PublicS3Bucket(Construct scope, String name){
              super(scope, name);

              new AwsProvider(this, "aws", AwsProviderConfig.builder()
                      .region("us-east-1")
                      .build()
              );

              this.bucket = new S3Bucket(this, "bucket", S3BucketConfig.builder()
                      .bucketPrefix(name)
                      .website(S3BucketWebsite.builder()
                              .indexDocument("index.html")
                              .errorDocument("5xx.html")
                              .build()
                      )
                      .build()
              );
          }
      }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.IO;
  using System.Collections.Generic;
  using System.Linq;
  using Constructs;
  using Io.Cdktn;
  using aws.Provider;
  using aws.S3Bucket;

  namespace Examples
  {

      class PublicS3Bucket : Construct
      {
          public S3Bucket bucket;
          public PublicS3Bucket(Construct scope, string name) : base(scope, name)
          {
              // This bucket is in a different scope than the buckets
              // defined in `MyStack`. Therefore, it does not need a unique name.
              this.bucket = new S3Bucket(this, "bucket", new S3BucketConfig
              {
                  BucketPrefix = name,
                  Website = new S3BucketWebsite
                  {
                      IndexDocument = "index.html",
                      ErrorDocument = "5xx.html"
                  }
              });
          }
      }

      class ConstructsScopeStack : TerraformStack
      {
          public ConstructsScopeStack(Construct scope, string name) : base(scope, name)
          {
              new AwsProvider(this, "Aws", new AwsProviderConfig
              {
                  Region = "us-east-1"
              });

              // Both buckets are inside of the same stack, meaning they share
              // the same scope. Therefore, their names must be unique.
              new PublicS3Bucket(this, "first-bucket");
              new PublicS3Bucket(this, "second-bucket");
          }
      }
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"github.com/aws/constructs-go/constructs/v10"
  	"github.com/aws/jsii-runtime-go"
  	"github.com/open-constructs/cdk-terrain-go/cdktn"
  	aws "github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/provider"
  	s3bucket "github.com/open-constructs/cdk-terrain/examples/go/documentation/generated/hashicorp/aws/s3bucket"
  )

  type PublicS3Bucket struct {
  	Bucket *s3bucket.S3Bucket
  }

  func NewPublicS3Bucket(scope constructs.Construct, name *string) *PublicS3Bucket {
  	c := constructs.NewConstruct(scope, name)

  	bucket := s3bucket.NewS3Bucket(c, name, &s3bucket.S3BucketConfig{
  		BucketPrefix: name,
  		Website: &s3bucket.S3BucketWebsite{
  			IndexDocument: jsii.String("index.html"),
  			ErrorDocument: jsii.String("5xx.html"),
  		},
  	})

  	return &PublicS3Bucket{
  		Bucket: &bucket,
  	}
  }

  func NewConstructsScopingStack(scope constructs.Construct, name string) cdktn.TerraformStack {
  	stack := cdktn.NewTerraformStack(scope, &name)

  	aws.NewAwsProvider(stack, jsii.String("aws"), &aws.AwsProviderConfig{
  		Region: jsii.String("us-east-1"),
  	})
  	NewPublicS3Bucket(stack, jsii.String("first-bucket"))
  	NewPublicS3Bucket(stack, jsii.String("second-bucket"))

  	return stack
  }

  func main() {
  	app := cdktn.NewApp(nil)

  	NewConstructsScopingStack(app, "constructs-scope")

  	app.Synth()
  }

  ```
</CodeGroup>

### Aspects

`Aspects` allow you to implement a visitor pattern, dynamically add or remove constructs, and automatically change attributes of existing constructs when you synthesize your CDTKF application. For example, you could use an aspect to help tag resources based on dynamic conditions. Refer to the [aspects documentation](/concepts/aspects) for more details.

### Available Constructs

You can search the Amazon Web Services (AWS) [Construct Hub](https://constructs.dev/search?q=\&cdk=cdktn\&offset=0) for existing CDKTN-compatible constructs. We are also building the [AWS Adapter](/create-and-deploy/aws-adapter), which lets you use AWS Constructs in your CDKTN projects.

<Note>The AWS Adapter is in tech preview.</Note>

## Write and Publish Constructs

You can write construct classes in any language and distribute them to other users. Refer to [Construct Design](/develop-custom-constructs/construct-design) and [Construct Publishing and Distribution](/develop-custom-constructs/publishing-and-distribution) for details.
