CloudFormation language extensions support
The AWS SAM CLI supports templates that use the AWS::LanguageExtensions transform, including
Fn::ForEach, Fn::Length, Fn::ToJsonString, and Fn::FindInMap with
DefaultValue. For complete reference information about these constructs, see AWS::LanguageExtensions transform in the AWS CloudFormation User Guide.
When the AWS SAM CLI detects AWS::LanguageExtensions in a template's Transform
section, it expands language extension constructs locally before running AWS SAM transforms. This enables
sam build, sam package, sam deploy, sam sync, sam validate,
sam local invoke, and sam local start-api to work with templates that use these constructs.
The expansion happens in two phases:
-
Phase 1 (Language Extensions) —
Fn::ForEachloops are expanded, intrinsic functions are resolved where possible, and the template is converted to standard CloudFormation. -
Phase 2 (AWS SAM Transform) — The expanded template is processed by the AWS SAM Translator as usual.
The original template (with Fn::ForEach intact) is preserved for CloudFormation deployment,
since CloudFormation processes the AWS::LanguageExtensions transform server-side.
Fn::ForEach
Fn::ForEach generates multiple resources, conditions, or outputs from a single template definition:
Transform: - AWS::LanguageExtensions - AWS::Serverless-2016-10-31 Parameters: ServiceNames: Type: CommaDelimitedList Default: "Users,Orders,Products" Resources: Fn::ForEach::Services: - Name - !Ref ServiceNames - ${Name}Function: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: python3.12 CodeUri: ./services/${Name}
Running sam build expands this into UsersFunction, OrdersFunction, and
ProductsFunction, each built from its respective source directory.
Dynamic artifact properties
When a packageable property uses a loop variable (for example, ./services/${Name}), the
AWS SAM CLI generates a CloudFormation Mappings section that maps each collection
value to its Amazon S3 URI. The Fn::ForEach body is rewritten to use
Fn::FindInMap so CloudFormation can resolve the correct artifact at deploy time.
The recognized artifact properties are the same ones sam package rewrites today. That
includes:
| Resource type | Property |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For dotted properties (for example, Command.ScriptLocation on
AWS::Glue::Job or Code.ImageUri on AWS::Lambda::Function), the
value is read and written at the nested location on the resource. The generated Mapping name uses only
the leaf segment of the property path.
When the property is loop-templated, the Mapping name is
SAM<LeafProperty><LoopName> (for example, SAMCodeUriServices or
SAMScriptLocationJobs).
Important
Customer-authored mappings should not start with these SAM* prefixes — they are
reserved for the AWS SAM CLI. See Limitations.
For example, after sam package:
Mappings: SAMCodeUriServices: Users: CodeUri: s3://my-bucket/abc123 Orders: CodeUri: s3://my-bucket/def456 Products: CodeUri: s3://my-bucket/ghi789 Resources: Fn::ForEach::Services: - Name - !Ref ServiceNames - ${Name}Function: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: python3.12 CodeUri: !FindInMap [SAMCodeUriServices, !Ref Name, CodeUri]
Multiple resources per ForEach body
A single Fn::ForEach body can emit more than one resource per iteration. Each resource is
generated for every collection value:
Resources: Fn::ForEach::Tables: - TableName - [Users, Orders, Products] - ${TableName}Table: Type: AWS::DynamoDB::Table Properties: TableName: !Sub "${AWS::StackName}-${TableName}" # ... ${TableName}StreamProcessor: Type: AWS::Serverless::Function Properties: CodeUri: stream-processors/${TableName}/ Events: DDBStream: Type: DynamoDB Properties: Stream: !GetAtt - !Sub "${TableName}Table" - StreamArn
Mapping name collision resolution
When two resources in the same Fn::ForEach body declare the same dynamic artifact property
(for example, both an Api and a StateMachine use DefinitionUri),
the AWS SAM CLI appends a suffix taken from the static portion of the resource
logical-ID template to keep Mapping names unique:
| Resource template | Property | Mapping name |
|---|---|---|
|
|
|
|
|
|
When there is no collision the base name (for example, SAMDefinitionUriServices) is used.
Parameter-based collections
When the Fn::ForEach collection is a parameter reference (for example, !Ref ServiceNames)
and the loop body uses a dynamic artifact property (for example, CodeUri: ./services/${Name}),
the AWS SAM CLI needs the collection values to generate the SAM* Mappings
described in Dynamic artifact properties. It resolves them when it
processes the template, from:
-
--parameter-overridespassed to the AWS SAM CLI command. -
The parameter's
Defaultvalue in the template.
Important
Because the SAM* Mappings are baked in at package time, you must re-package whenever
you change the parameter value (for example, when adding a new service) so the Mappings include
entries for the new values. This applies only when the parameter drives a dynamic artifact loop;
other parameter overrides can be changed at deploy time as usual.
# Package with the values you intend to deploy with sam package --parameter-overrides ServiceNames="Users,Orders,Products" # Deploy with the same values sam deploy --parameter-overrides ServiceNames="Users,Orders,Products"
Nested stacks
Fn::ForEach in nested stack templates (AWS::CloudFormation::Stack) is supported.
The AWS SAM CLI passes the parent stack's Parameters property to the child template
expansion, so child Fn::ForEach collections that reference parent-supplied parameters resolve correctly.
# parent.yaml Resources: ChildStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: ./child.yaml Parameters: ServiceNames: "Users,Orders,Products"
Nested Fn::ForEach
Up to 5 levels of nesting are supported, matching the CloudFormation limit:
Resources: Fn::ForEach::Envs: - Env - [Dev, Staging, Prod] - Fn::ForEach::Services: - Svc - [Users, Orders] - ${Env}${Svc}Function: Type: AWS::Serverless::Function Properties: CodeUri: ./services/${Svc} Environment: Variables: STAGE: ${Env}
ForEach in Outputs
Fn::ForEach blocks are also expanded inside the Outputs section, so you can emit
one output per collection value:
Outputs: Fn::ForEach::FunctionArns: - Name - [alpha, beta] - ${Name}FunctionArn: Value: !GetAtt - !Sub "${Name}Function" - Arn
Conditions and DependsOn
Resources emitted by Fn::ForEach can carry Condition and DependsOn
like any other resource. The condition or dependency is replicated onto each generated resource:
Conditions: IsProd: !Equals [!Ref Environment, prod] Resources: SharedTable: Type: AWS::DynamoDB::Table # ... Fn::ForEach::Functions: - Name - [api, worker] - ${Name}Function: Type: AWS::Serverless::Function Condition: IsProd DependsOn: SharedTable Properties: Handler: main.handler CodeUri: functions/${Name}/
&{identifier} syntax
The &{identifier} syntax strips non-alphanumeric characters from the substituted value,
which is useful for generating valid logical IDs from values such as IP addresses:
Fn::ForEach::Hosts: - IP - ["10.0.0.1", "10.0.0.2"] - Host&{IP}: Type: AWS::EC2::Instance # Expands to Host10001, Host10002
Supported intrinsic functions
The following intrinsic functions are resolved locally during expansion:
| Function | Description |
|---|---|
|
Loop expansion. |
|
Returns the count of list elements. |
|
Converts a value to a JSON string. |
|
Map lookup, including the optional |
|
Conditional value selection. |
|
String substitution. |
|
String concatenation. |
|
String splitting. |
|
List element selection. |
|
Base64 encoding. |
|
Condition evaluation. |
|
Parameter and pseudo-parameter references. |
Functions that require deployed resources (Fn::GetAtt, Fn::ImportValue,
Fn::GetAZs) are preserved for CloudFormation to resolve at deploy time.
Validation errors
The following template issues are caught locally before the AWS SAM transform runs:
| Cause | Error message |
|---|---|
The |
|
More than 5 levels of |
|
The collection resolves to an empty list (for example, a
|
No error — the loop is silently skipped and no resources are emitted. |
The |
No error — the unresolved reference is preserved in the template. At deploy time, CloudFormation will resolve it server-side. |
Limitations
-
Collections must be resolvable at build/package time.
Fn::ForEachcollections that useFn::GetAtt,Fn::ImportValue, or SSM/Secrets Manager dynamic references cannot be expanded locally. Use a parameter with--parameter-overridesinstead. -
Dynamic artifact mappings are fixed at package time. When a
Fn::ForEachcollection is a parameter reference and the loop body uses a dynamic artifact property (for example,CodeUri: ./services/${Name}), the generatedSAM*Mappings only contain entries for the parameter values that were resolved at package time. If you change--parameter-overridesfor that parameter at deploy time without re-packaging, the new values won't have Mapping entries and deployment will fail. This does not apply to parameters that aren't used to drive a dynamic artifactFn::ForEach. -
DeletionPolicyandUpdateReplacePolicyare validated and resolved during expansion. They supportRefto parameters but not other intrinsic functions. -
Nesting limit. Up to 5 levels of
Fn::ForEachmay be nested, matching the CloudFormation server-side limit. -
Reserved Mapping names. Mapping names starting with any of the following are reserved for the AWS SAM CLI — do not author your own mappings with these prefixes:
-
SAMCodeUri,SAMImageUri,SAMContentUri,SAMDefinitionUri,SAMSchemaUri,SAMBodyS3Location,SAMDefinitionS3Location,SAMTemplateURL,SAMCode,SAMContent— emitted bysam packagefor dynamic artifact properties. See the Dynamic artifact properties table. -
SAMLayers— emitted bysam buildwhen anFn::ForEach-generated function picks up auto-generated dependency-layer references (Lambda layers the AWS SAM CLI builds into a nested stack). This prefix has no corresponding user-authored property; it is added automatically.
-