Skip to main content
A mixin is a reusable piece of functionality that you apply to a specific construct to add behavior, properties, or modify existing configuration — without subclassing. You apply mixins imperatively with the construct.with(...) method, and each mixin decides which constructs it supports. Mixins are the per-construct counterpart to Aspects: a mixin is applied immediately to the one construct you hand it, while an aspect sweeps every construct in a scope during synthesis. See Mixins vs. Aspects below for how to choose.
The mixin primitive — the IMixin interface and construct.with(...mixins) — has been available in CDK Terrain (CDKTN) since 0.23.0 (cdk-terrain#164), which raised the constructs peer dependency to ^10.6.0 (cdk-terrain#20); CDKTN inherits it from that library. CDKTN does not yet bundle any ready-made mixins in core. You write your own today (this page shows how). Future core releases may ship common mixins based on community use cases, requests, and contributions — the direction is being discussed in RFC-05 in the planning repo; comments and use cases are welcome there.

Define a mixin

A mixin is a class that implements the IMixin interface, which has two methods:
  • supports(construct) — return true if this mixin can be applied to the given construct. Use it as a type guard so applyTo only runs on compatible resources.
  • applyTo(construct) — apply the change. This runs immediately when with() is called.
CDKTN gives you two building blocks for applyTo:
  • TerraformResource.isTerraformResource(x) — a provider-agnostic guard for supports.
  • element.addOverride(path, value) — a provider-agnostic escape hatch to set any argument, plus typed setters on TerraformResource such as lifecycle, provider, dependsOn, count, and forEach.

Example: PreventDestroy (provider-agnostic)

This mixin works on any Terraform resource, from any provider. It sets the resource’s lifecycle.prevent_destroy so Terraform refuses to destroy it.
TypeScript
import { IConstruct, IMixin } from "constructs";
import { TerraformResource } from "cdktn";

export class PreventDestroy implements IMixin {
  supports(construct: IConstruct): boolean {
    return TerraformResource.isTerraformResource(construct);
  }

  applyTo(construct: IConstruct): void {
    const resource = construct as TerraformResource;
    resource.lifecycle = { ...resource.lifecycle, preventDestroy: true };
  }
}
Apply it to a single construct with with(), which returns the same construct so you can keep a reference:
TypeScript
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";

const bucket = new S3Bucket(this, "data", { bucket: "my-data" }).with(
  new PreventDestroy(),
);
You can apply several mixins at once; they run in order:
TypeScript
new S3Bucket(this, "data", { bucket: "my-data" }).with(
  new PreventDestroy(),
  new EnableVersioning(),
);

Example: EnableVersioning (provider-specific)

A mixin can narrow supports to a specific resource type and configure it. Here addOverride sets the versioning block without depending on a particular provider version’s typed API.
TypeScript
import { IConstruct, IMixin } from "constructs";
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";

export class EnableVersioning implements IMixin {
  supports(construct: IConstruct): boolean {
    return construct instanceof S3Bucket;
  }

  applyTo(construct: IConstruct): void {
    (construct as S3Bucket).addOverride("versioning", { enabled: true });
  }
}
The inline versioning block on aws_s3_bucket (used here and in the next example) is deprecated by the AWS provider in favor of the separate aws_s3_bucket_versioning resource — a real terraform plan prints a deprecation warning. It is kept here only to make the mixin self-contained; the mixin technique is identical whichever resource you target.

Example: DataRecovery (multiple resource types)

One mixin can support more than one resource type and apply the right change to each — a single “data recovery” feature that enables S3 versioning and DynamoDB point-in-time recovery. It is still applied per-construct with with().
TypeScript
import { IConstruct, IMixin } from "constructs";
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
import { DynamodbTable } from "./.gen/providers/aws/dynamodb-table";

export class DataRecovery implements IMixin {
  supports(construct: IConstruct): boolean {
    return construct instanceof S3Bucket || construct instanceof DynamodbTable;
  }

  applyTo(construct: IConstruct): void {
    if (construct instanceof S3Bucket) {
      construct.putVersioning({ enabled: true });
    }
    if (construct instanceof DynamodbTable) {
      construct.putPointInTimeRecovery({ enabled: true });
    }
  }
}

Mixins vs. Aspects

Mixins and Aspects are complementary. The choice is about where the effect applies and when — not whether it mutates configuration.
MixinAspect
Applied toone construct you holdevery construct in a scope
Whenimmediately, at with()deferred, during synthesis
Reaches constructs you don’t hold (nested, from a library, added later)?NoYes
Good foropting a specific resource into a featureblanket sweeps — tagging, dependency propagation, and validation
Use a mixin when you have the construct instance and want to opt it in explicitly, with a type-safe supports check. Use an aspect when you want to cover a whole subtree regardless of what is in it.
Mutation is not exclusive to mixins. Tagging every resource in a stack is a classic mutating aspect — you don’t hold each resource, and you want new and nested resources covered too. Don’t rewrite a scope-wide tagging or dependency-propagation aspect as a mixin; keep it an aspect.

The complementary pattern: configure with a mixin, validate with an aspect

A common pairing is a mixin that configures a resource and an aspect that validates the whole app meets a policy — including resources created by third-party libraries that you never call with() on. This example enforces IMDSv2 on EC2 instances:
TypeScript
import { IConstruct, IMixin } from "constructs";
import { Aspects, IAspect, Annotations } from "cdktn";
import { Instance } from "./.gen/providers/aws/instance";

// Mixin — opt a specific instance in (imperative, immediate)
export class RequireImdsv2 implements IMixin {
  supports(construct: IConstruct): boolean {
    return construct instanceof Instance;
  }
  applyTo(construct: IConstruct): void {
    (construct as Instance).putMetadataOptions({ httpTokens: "required" });
  }
}

// Aspect — govern the whole app (deferred, validating)
export class ValidateImdsv2 implements IAspect {
  visit(node: IConstruct): void {
    if (
      node instanceof Instance &&
      node.metadataOptionsInput?.httpTokens !== "required"
    ) {
      Annotations.of(node).addError("EC2 instances must require IMDSv2 (httpTokens: required).");
    }
  }
}

// Configure the instances you create...
new Instance(this, "web", { ami: "ami-123", instanceType: "t3.micro" }).with(
  new RequireImdsv2(),
);

// ...and validate everything, including instances you didn't create yourself.
Aspects.of(this).add(new ValidateImdsv2());
Because ValidateImdsv2 calls Annotations.of(node).addError(...), this is a hard gate, not just an annotation: any Instance that reaches synthesis without IMDSv2 makes app.synth() fail — the error names the offending construct’s path — rather than merely warning. That is what makes the aspect an enforcement backstop for resources nobody remembered to .with(new RequireImdsv2()). Use addWarning instead of addError if you want a nudge rather than a gate.
These examples are shown in TypeScript. The construct.with(...) primitive is available in every CDKTN language through the inherited constructs.Construct method — note the language-specific names: Python exposes it as with_ and Go/C# as With. See the API reference for the signature in each language.
  • Aspects — apply an operation to every construct in a scope.
  • Resources — the lifecycle block, escape hatches, and addOverride.
  • RFC-05: Mixins — the design discussion and the mixin-vs-aspect analysis behind this page.
  • Announcing AWS CDK Mixins — the AWS CDK feature this primitive mirrors (CDKTN inherits it from the shared constructs library).