CloudFormation protip: use !Sub instead of !Join

Update 12/05/2019: as Moshe pointed out in the comments, Fn::Sub is not supported by the Serverless framework because it too uses the ${} syntax to support its own variables system. However, as is often the case with the Serverless framework, you can work around this issue with a plugin. Check out the serverless-cloudformation-sub-variables plugin which lets you use Fn::Sub in the serverless.yml. You just need to use #{VariableName} instead of ${VariableName}.

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.

Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.
Subscribe to my newsletter


Hi, I’m Yan. I’m an AWS Serverless Hero and the author of Production-Ready Serverless.

I specialise in rapidly transitioning teams to serverless and building production-ready services on AWS.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

Hire me.


Check out my new course, Complete Guide to AWS Step Functions. In this course, we’ll cover everything you need to know to use AWS Step Functions service effectively. Including basic concepts, HTTP and event triggers, activities, callbacks, nested workflows, design patterns and best practices.

Get Your Copy


Come learn about operational BEST PRACTICES for AWS Lambda: CI/CD, testing & debugging functions locally, logging, monitoring, distributed tracing, canary deployments, config management, authentication & authorization, VPC, security, error handling, and more.

You can also get 40% off the face price with the code ytcui.

Get Your Copy