You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(cfn-lang-ext): expand CloudFormation Language Extensions reference
Adds documentation for behaviors that are now supported / verified by this
PR's coverage:
- Full table of recognized dynamic artifact properties (derived from the
canonical packageable-resource list rather than a hand-maintained one),
including AWS::Serverless::Application, AWS::CloudFormation::StackSet,
AppSync, ElasticBeanstalk, Glue::Job, Lambda::Function image package
- Note about how dotted property paths (Command.ScriptLocation,
Code.ImageUri) are addressed on the resource vs. used as Mapping
identifiers
- Multiple resources per ForEach body, Mapping name collision resolution
- ForEach in Outputs; Conditions and DependsOn on emitted resources
- Validation errors (ForEach element count, nesting limit, missing
parameter, empty collection)
Copy file name to clipboardExpand all lines: docs/cfn-language-extensions.md
+125-1Lines changed: 125 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,7 +41,37 @@ Running `sam build` expands this into `UsersFunction`, `OrdersFunction`, and `Pr
41
41
42
42
### Dynamic artifact properties
43
43
44
-
When a packageable property (like `CodeUri`, `ContentUri`, `ImageUri`) uses a loop variable (e.g., `./services/${Name}`), SAM CLI generates a CloudFormation `Mappings` section that maps each collection value to its S3 URI. The `Fn::ForEach` body is rewritten to use `Fn::FindInMap` so CloudFormation can resolve the correct artifact at deploy time.
44
+
When a packageable property uses a loop variable (e.g., `./services/${Name}`), SAM CLI generates a CloudFormation `Mappings` section that maps each collection value to its S3 URI. The `Fn::ForEach` body is rewritten to use `Fn::FindInMap` so CloudFormation can resolve the correct artifact at deploy time.
45
+
46
+
The set of recognized artifact properties is derived from the same canonical list `sam package` already uses (`RESOURCES_WITH_LOCAL_PATHS` and `RESOURCES_WITH_IMAGE_COMPONENT` in `samcli/lib/utils/resources.py`), so every resource type whose artifact property `sam package` would normally rewrite is supported here too. That includes:
When a property is dotted (e.g. `Command.ScriptLocation` on `AWS::Glue::Job` or `Code.ImageUri` on `AWS::Lambda::Function`), SAM CLI reads and writes the value at the dotted location on the resource — so it lands at `Properties.Command.ScriptLocation` rather than at a literal `Properties["Command.ScriptLocation"]` key — and uses only the leaf segment when it needs to construct an alphanumeric identifier (Mapping name suffix or `Fn::FindInMap` third argument).
73
+
74
+
When the property is loop-templated, the Mapping name is `SAM<LeafProperty><LoopName>` (e.g., `SAMCodeUriServices`, `SAMScriptLocationJobs`). Customer-authored mappings should not start with these `SAM*` prefixes — they are reserved for SAM CLI (see [Limitations](#limitations) below).
A single `Fn::ForEach` body can emit more than one resource per iteration. Each resource is generated for every collection value:
103
+
104
+
```yaml
105
+
Resources:
106
+
Fn::ForEach::Tables:
107
+
- TableName
108
+
- [Users, Orders, Products]
109
+
- ${TableName}Table:
110
+
Type: AWS::DynamoDB::Table
111
+
Properties:
112
+
TableName: !Sub "${AWS::StackName}-${TableName}"
113
+
# ...
114
+
115
+
${TableName}StreamProcessor:
116
+
Type: AWS::Serverless::Function
117
+
Properties:
118
+
CodeUri: stream-processors/${TableName}/
119
+
Events:
120
+
DDBStream:
121
+
Type: DynamoDB
122
+
Properties:
123
+
Stream: !GetAtt
124
+
- !Sub "${TableName}Table"
125
+
- StreamArn
126
+
```
127
+
128
+
### Mapping name collision resolution
129
+
130
+
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`), SAM CLI appends a sanitized suffix derived from the resource logical-ID template to keep Mapping names unique:
When there is no collision the base name (e.g., `SAMDefinitionUriServices`) is used.
138
+
70
139
### Parameter-based collections
71
140
72
141
When the `Fn::ForEach` collection is a parameter reference (`!Ref ServiceNames`), the collection values are resolved at package time from:
@@ -120,6 +189,46 @@ Resources:
120
189
STAGE: !Ref Env
121
190
```
122
191
192
+
### ForEach in Outputs
193
+
194
+
`Fn::ForEach` blocks are also expanded inside the `Outputs` section, so you can emit one output per collection value:
195
+
196
+
```yaml
197
+
Outputs:
198
+
Fn::ForEach::FunctionArns:
199
+
- Name
200
+
- [alpha, beta]
201
+
- ${Name}FunctionArn:
202
+
Value: !GetAtt
203
+
- !Sub "${Name}Function"
204
+
- Arn
205
+
```
206
+
207
+
### Conditions and DependsOn
208
+
209
+
Resources emitted by `Fn::ForEach` can carry `Condition` and `DependsOn` like any other resource. The condition or dependency is replicated onto each generated resource:
210
+
211
+
```yaml
212
+
Conditions:
213
+
IsProd: !Equals [!Ref Environment, prod]
214
+
215
+
Resources:
216
+
SharedTable:
217
+
Type: AWS::DynamoDB::Table
218
+
# ...
219
+
220
+
Fn::ForEach::Functions:
221
+
- Name
222
+
- [api, worker]
223
+
- ${Name}Function:
224
+
Type: AWS::Serverless::Function
225
+
Condition: IsProd
226
+
DependsOn: SharedTable
227
+
Properties:
228
+
Handler: main.handler
229
+
CodeUri: functions/${Name}/
230
+
```
231
+
123
232
### &{identifier} syntax
124
233
125
234
The `&{identifier}` syntax strips non-alphanumeric characters from the substituted value, useful for generating valid logical IDs from values like IP addresses:
@@ -154,11 +263,26 @@ The following intrinsic functions are resolved locally during expansion:
154
263
155
264
Functions that require deployed resources (`Fn::GetAtt`, `Fn::ImportValue`, `Fn::GetAZs`) are preserved for CloudFormation to resolve at deploy time.
156
265
266
+
## Validation errors
267
+
268
+
The following template issues are caught locally before the SAM transform runs:
269
+
270
+
| Error | Cause |
271
+
|-------|-------|
272
+
| `Fn::ForEach must have exactly 3 elements` | The `Fn::ForEach` value is not a 3-element list (`[loop_variable, collection, output_template]`). |
273
+
| `Maximum nesting depth of 5 exceeded` | More than 5 levels of `Fn::ForEach` are nested. |
274
+
| `Parameter '<name>' referenced by Fn::ForEach not found` | The `!Ref` in the collection points at a parameter that is not declared in the template. |
275
+
| Empty collection | If the collection resolves to an empty list (e.g., a `CommaDelimitedList` parameter with `Default: ""`), no resources are emitted — the loop is silently skipped. |
276
+
157
277
## Limitations
158
278
159
279
- **Collections must be resolvable at build/package time.** `Fn::ForEach` collections that use `Fn::GetAtt`, `Fn::ImportValue`, or SSM/Secrets Manager dynamic references cannot be expanded locally. Use a parameter with `--parameter-overrides` instead.
160
280
- **Parameter values are fixed at package time.** If you change `--parameter-overrides` at deploy time without re-packaging, the Mappings won't include entries for new values and deployment will fail.
161
281
- **`DeletionPolicy` and `UpdateReplacePolicy`** are validated and resolved during expansion. They support `Ref` to parameters but not other intrinsic functions.
282
+
- **Nesting limit.** Up to 5 levels of `Fn::ForEach` may be nested, matching CloudFormation's server-side limit.
283
+
- **Reserved Mapping names.** Mapping names starting with any of the following are reserved for SAM CLI — do not author your own mappings with these prefixes:
284
+
- `SAMCodeUri`, `SAMImageUri`, `SAMContentUri`, `SAMDefinitionUri`, `SAMSchemaUri`, `SAMBodyS3Location`, `SAMDefinitionS3Location`, `SAMTemplateURL`, `SAMCode`, `SAMContent` — emitted by `sam package` for dynamic artifact properties (see the table above).
285
+
- `SAMLayers`— emitted by `sam build` when a `Fn::ForEach`-generated function picks up auto-generated dependency-layer references (Lambda layers SAM CLI builds into a nested stack). This prefix has no corresponding user-authored property; it is added automatically.
0 commit comments