CloudFormation protip: use !Sub instead of !Join

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:

  1. 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 to level up your serverless skills quickly.
  2. Do you want to know how to test serverless architectures with a fast dev & test loop? Check out my latest course, Testing Serverless Architectures and learn the smart way to test serverless.
  3. 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.