Skip to main content
This article demonstrates how to use the CloudFormation !Sub substitution function to inject parameter values into ARNs and other strings, avoiding hard-coded resource names. Using !Sub makes templates reusable, easier to manage across environments, and helps eliminate brittle string literals.

Why use !Sub instead of hard-coded names

  • Keeps templates environment-agnostic and reusable.
  • Lets you supply values at stack creation time via Parameters.
  • Reduces copy/paste errors when reusing templates across accounts, regions, or partitions.

Example: Hard-coded bucket name

Here is an example policy where the bucket name is hard-coded inside the ARN:
Resources:
  MyPublicReadPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref InputBucketName
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: "*"
            Action: "s3:GetObject"
            Resource: "arn:aws:s3:::eden-kodekloud-bncv-bkt/*"
The eden-kodekloud-bncv-bkt segment is fixed in the template. To make this template parameter-driven, replace the literal bucket name in the ARN with a !Sub expression that references a parameter.

Dynamic resource ARN with !Sub

Use !Sub with a placeholder like ${InputBucketName}. You can keep the Bucket property as !Ref (or use !Sub there as well). This example keeps Bucket: !Ref and uses !Sub only in the ARN:
Resources:
  MyPublicReadPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref InputBucketName
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: "*"
            Action: "s3:GetObject"
            Resource: !Sub "arn:aws:s3:::${InputBucketName}/*"
Now the ${InputBucketName} placeholder is replaced with the value provided to the InputBucketName parameter when the stack is created.

Parameters and mappings (example)

Below is an example Mappings and Parameters section that pairs a bucket parameter with a developer selection. This demonstrates how !Sub works together with template inputs:
Mappings:
  DevMap:
    Arno:
      Field: "Quality assurance"
      Env: "Testing/development"
    Alice:
      Field: "Backend developer"
      Env: "Production"

Parameters:
  InputBucketName:
    Type: String
    Description: "Please enter your desired S3 bucket name"

  InputDeveloperName:
    Type: String
    Description: "Select the developer"
    AllowedValues:
      - Arno
      - Alice
Using !Sub improves template readability and reusability. You can reference parameters in any string or ARN using the same ${ParamName} pattern, and the substitution happens at deploy time.

cfn-lint warning about hard-coded partition

Tooling like cfn-lint may warn about a hard-coded partition when an ARN includes arn:aws:.... That literal uses the aws partition and reduces portability if you plan to deploy to specialized partitions (for example, aws-us-gov or aws-cn). To make an ARN partition-agnostic, include the AWS::Partition pseudo parameter inside !Sub:
Resources:
  MyPublicReadPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref InputBucketName
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal: "*"
            Action: "s3:GetObject"
            Resource: !Sub "arn:${AWS::Partition}:s3:::${InputBucketName}/*"
This ensures the partition (aws, aws-cn, aws-us-gov, etc.) is chosen automatically based on where the stack is deployed.
If you do not plan to deploy to AWS GovCloud or China partitions, keeping arn:aws:s3::: is acceptable in many cases. Use ${AWS::Partition} when you need true portability across partitions.

Linter severities (quick reference)

SeverityMeaningAction
Red (Error)Will prevent deploymentFix immediately
Yellow (Warning)Likely a configuration or portability issueEvaluate and address as needed
Blue / InfoBest-practice suggestionOptional; follow if applicable

Best practices & checklist

  • Replace hard-coded resource names in ARNs with !Sub and ${ParameterName} to support parameterized deployments.
  • Use AWS::Partition inside !Sub if you require portability across AWS partitions.
  • Prefer !Sub for any string that includes parameters or pseudo parameters (for example, AWS::AccountId, AWS::Region, AWS::Partition).
  • Keep Bucket: !Ref InputBucketName (or Bucket: !Sub "${InputBucketName}") and use !Sub only where interpolation is required.
  • Run cfn-lint to catch portability and formatting issues, and apply linter guidance based on your deployment targets.

Summary

  • Use !Sub with ${ParameterName} to inject parameter values into ARNs and other strings.
  • Add ${AWS::Partition} inside !Sub when cross-partition portability is needed.
  • Treat cfn-lint messages as guidance: prioritize errors, evaluate warnings, and apply best-practice suggestions where beneficial.

Watch Video