Yan Cui
I help clients go faster for less using serverless technologies.
CloudFormation supports a number of intrinsic functions and Fn::Join (or !Join) is often used to construct parameterised names and paths.
The Serverless framework, for instance, uses it extensively. A quick look in a CloudFormation it generates I can see Fn::Join used for:
- IAM policy names
- IAM role names
- IAM principals
- API Gateway URIs
- Resource ARNs
and many more.
But it’s not just the frameworks that are using Fn::Join heavily. They also show up in our own code all the time as well. For example, to construct the ARN for a resource, or the URI for an API Gateway endpoint.


I find these very hard to comprehend, and my protip for you today is to use Fn::Sub (or the !Sub shorthand) instead.
Many folks would use Fn::Sub when they need to reference pseudo parameters such as AWS::Region and AWS::AccountId, for example:
!Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:vpc/${vpc}'
But Fn::Sub also lets you provide your own parameters. For example:
!Sub
- 'arn:aws:s3:::${Bucket}/*'
- { Bucket: Ref MyBucket }
The advantage over Fn::Join is that you can see the pattern of the interpolated string. Whereas with Fn::Join you’ll have to construct the pattern in your mind, which requires far more cognitive energy.
!Join
- ''
- - 'arn:aws:s3:::'
- !Ref MyBucket
- '/*'
Here are a few side-by-side comparisons to drive home the message.
Example 1: IAM role name
RoleName: # hello-world-dev-{region}-lambdaRole
!Join
- '-'
- - 'hello-world'
- 'dev'
- !Ref 'AWS::Region'
- 'lambdaRole'
with Fn::Sub:
PolicyName:
!Sub 'hello-world-dev-${AWS::Region}-lambdaRole
Example 2: API Gateway integration URI
Uri: # arn:{partition}:apigateway:{region}:.../{lambda}/invocations
!Join
- ''
- - 'arn:'
- Ref: AWS::Partition
- ':apigateway:'
- Ref: AWS::Region
- ':lambda:path/2015-03-31/functions/'
- !GetAtt 'HelloLambdaFunction.Arn'
- '/invocations'
with Fn::Sub:
Uri:
!Sub
- 'arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015/03/31/functions/${Function}/invocations'
- { Function: !GetAtt 'HelloLambdaFunction.Arn' }
Example 3: Lambda permission for API Gateway
SourceArn: # arn:{partition}:execute-api:{region}:.../*/*
!Join:
- ''
- - 'arn:'
- Ref: AWS::Partition
- ':execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- Ref: ApiGatewayRestApi
- '/*/*'
with Fn::Sub:
SourceArn:
!Sub
- 'arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*'
- { RestApi: Ref: ApiGatewayRestApi }
Suffice to say that the Fn::Sub version is easier to understand in every case! Now that you have seen what Fn::Sub can do, I hope you will prefer it to Fn::Join going forward.
Finally, if you’re using the Serverless framework and need more expressive power than the intrinsic functions can offer, then check out this plugin. It lets you use a number of “extrinsic” functions such as Fn::Substring or Fn::StartsWith anywhere in your serverless.yml.
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.
