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
Apply it to a single construct with with(), which returns the same construct so you can keep a reference:
TypeScript
You can apply several mixins at once; they run in order:
TypeScript

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
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

Mixins vs. Aspects

Mixins and Aspects are complementary. The choice is about where the effect applies and when — not whether it mutates configuration. 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
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).