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
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
+
| Cause | Error message |
271
+
|-------|---------------|
272
+
| The `Fn::ForEach` value is malformed — not a list, doesn't have exactly 3 elements, or has a non-string loop identifier. | `Fn::ForEach::<key> layout is incorrect` (raised as `InvalidTemplateException`; see `samcli/lib/cfn_language_extensions/processors/foreach.py`). |
273
+
| More than 5 levels of `Fn::ForEach` are nested. | `Fn::ForEach nesting depth of <N> exceeds the maximum allowed depth of 5. CloudFormation supports up to 5 nested Fn::ForEach loops.` |
274
+
| The collection resolves to an empty list (e.g., a `CommaDelimitedList` parameter with `Default: ""`). | No error — the loop is silently skipped and no resources are emitted. |
275
+
| The `!Ref` in the collection points at a parameter that is not declared in the template. | No error in the typical `sam build` / `sam package` flow. SAM CLI runs intrinsic resolution in PARTIAL mode and preserves the unresolved `{"Ref": "<name>"}`. CloudFormation will reject the unresolved ref at deploy time. |
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