`Fn::Sub` (`!Sub` in YAML) substitutes variables into a string at template evaluation time. It replaces the older pattern of building strings with `Fn::Join`, which produced unreadable nested arrays of fragments. ## The three substitution patterns ```yaml # 1. Reference a Parameter or Resource (equivalent to !Ref inside a string) !Sub "Hello ${StackName}" # 2. Reference a Resource attribute (equivalent to !GetAtt) !Sub "${ApplicationLoadBalancer.DNSName}" # 3. Reference a pseudo parameter !Sub "arn:aws:s3:::${AWS::AccountId}-bucket" ``` A common composite use — building an ARN with multiple substitutions: ```yaml !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${MyNotificationTopic}" ``` ## Local variable map (rarely needed) `Fn::Sub` accepts a second argument: a map of substitution names to values. Useful when interpolating something that isn't already a Parameter / Resource / pseudo parameter: ```yaml !Sub - "Welcome to ${Environment}" - Environment: !If [IsProd, "production", "staging"] ``` The map shadows template-level names. Without the map, `!Sub` resolves names against Parameters → Resources → Pseudo parameters in that order. ## Escape sequence: `${!Literal}` To emit a literal `${...}` in the output (e.g., a shell variable in `UserData`), prefix with `!`: ```yaml UserData: !Base64 | #!/bin/bash echo "Region is ${AWS::Region}" # CFN substitutes echo "Home is ${!HOME}" # output: Home is ${HOME} ``` This is the most common gotcha when embedding bash scripts that use `${}` for shell expansion. ## Why `Fn::Sub` beat `Fn::Join` The pre-Sub pattern for an ARN looked like this: ```yaml !Join - '' - - 'arn:aws:sns:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':' - !Ref MyTopic ``` Versus the `!Sub` equivalent (one line, readable). Use `Fn::Join` only when: - You're joining a **list of items** (not just splicing into one string) — `!Join [",", !Ref MyList]` - You need a delimiter that's not just empty string — `!Join ["/", [a, b, c]]` For everything else, `!Sub` is the modern default. `cfn-lint` and `rain fmt` will often suggest converting `Join` patterns to `Sub`. ## What `Fn::Sub` cannot do - **No conditionals** in the substitution string — use `Fn::If` to choose between two values, then pass into `Sub` - **No expressions** — just variable substitution; no arithmetic, no string manipulation - **No nested intrinsic functions inside `${...}`** — only names. To inject a computed value, use the local variable map ## Related - [[CFN Ref vs Fn-GetAtt]] - [[CFN Pseudo Parameters]] - [[CFN Conditions Boolean Logic]]