Skip to content

Multi-Stack Projects

chant supports splitting a project across multiple stacks in two ways:

  • Directory-based partitioning — a core chant feature. Subdirectories under src/ each become a separate stack, with cross-stack references resolved automatically.
  • Child projects (nested stacks) — a lexicon-specific feature. A parent file references a child project directory explicitly (e.g. AWS nestedStack()), and the child’s stackOutput() declarations wire the cross-stack references.

Both modes emit separately-deployable output files. Pick based on how you want the pieces to relate at deploy time: partitioning gives you independent stacks with no orchestrator; child projects give you a parent that references and can deploy the children as a unit.

Organize src/ into subdirectories — each becomes its own stack in dist/. Resources in one stack can reference resources in another through normal TypeScript imports; chant translates those into provider-native cross-stack exports/imports (CloudFormation Export/ImportValue, and so on).

my-project/
├── chant.config.ts
├── src/
│ ├── networking/
│ │ ├── vpc.ts
│ │ └── subnets.ts
│ ├── compute/
│ │ ├── api-function.ts
│ │ └── worker-function.ts
│ └── storage/
│ ├── data-table.ts
│ └── assets-bucket.ts
└── dist/
├── networking.json
├── compute.json
├── storage.json
└── manifest.json

See Multi-Stack Output for how chant detects single-stack vs. multi-stack mode and the full set of cross-stack primitives.

A child project is a subdirectory that builds to a separately-valid template, referenced explicitly from a parent file. Unlike partitioning, the parent is aware of the child — it uses a lexicon-specific function like nestedStack() and can pass parameters in and read outputs back. Cross-stack values are declared explicitly with stackOutput().

For example, the AWS lexicon’s nestedStack() function references a child project directory and produces separate CloudFormation child templates:

my-project/
├── src/
│ ├── app.ts ← parent resources + nestedStack() call
│ └── network/ ← child project
│ ├── vpc.ts
│ ├── security.ts
│ └── outputs.ts ← stackOutput() declarations
└── dist/
├── template.json ← parent (AWS::CloudFormation::Stack)
└── network.template.json ← child (standalone template)
src/network/outputs.ts
/**
* Cross-stack outputs — values the parent can reference
*/
import { stackOutput } from "@intentius/chant";
import { vpc, subnet } from "./vpc";
import { lambdaSg } from "./security";
export const vpcId = stackOutput(vpc.VpcId, {
description: "VPC ID",
});
export const subnetId = stackOutput(subnet.SubnetId, {
description: "Public subnet ID",
});
export const lambdaSgId = stackOutput(lambdaSg.GroupId, {
description: "Lambda security group ID",
});
src/app.ts
/**
* App layer — Lambda function in the parent template that references
* the network nested stack's outputs via cross-stack references
*/
import { Function, Sub, AWS, Ref, nestedStack } from "@intentius/chant-lexicon-aws";
// nestedStack() references a child project directory
const network = nestedStack("network", import.meta.dirname + "/network", {
parameters: { Environment: "prod" },
});
export const handler = new Function({
FunctionName: Sub`${AWS.StackName}-handler`,
Runtime: "nodejs20.x",
Handler: "index.handler",
Role: Ref("LambdaExecutionRole"),
Code: { ZipFile: "exports.handler = async () => ({ statusCode: 200 });" },
VpcConfig: {
SubnetIds: [network.outputs.subnetId],
SecurityGroupIds: [network.outputs.lambdaSgId],
},
});
// Re-export so discovery picks it up as an entity
export { network };

The child can be built independently (chant build src/network/), and the parent build produces multiple template files. See your lexicon’s documentation for details — for AWS, see the Nested Stacks guide.