Paste a CloudFormation YAML template, short-form tags, comments and all, and get Terraform HCL back with resources, variables and outputs you can validate and import.
This looks like it contains a secret.
Your code is sent to an AI service for conversion. Replace real keys and tokens with placeholders before converting.
How to convert a CloudFormation YAML template to Terraform
To convert CloudFormation YAML to Terraform, paste the template above and press Convert to Terraform. Tags such as !Ref, !GetAtt and !Sub become Terraform references and interpolation, Parameters become variables and Conditions become count or conditional expressions. Validate the HCL, then import the existing resources.
Expand transforms firstIf the template starts with Transform: AWS::Serverless-2016-10-31 or uses macros, download the processed version with aws cloudformation get-template --stack-name NAME --template-stage Processed. That's the template with the real resources in it.
Paste or open the YAMLPaste it into the CloudFormation (YAML) editor, or open a .yaml, .yml, .template or .txt file up to 500 KB. Short-form and full-form functions can be mixed. The page checks for keys and tokens before anything is sent.
Convert to TerraformClick Convert to Terraform or press Ctrl + Enter. The HCL streams in, and you can stop it at any time. Up to 60,000 characters fit in one conversion.
Review the tricky partsCheck conditions, !Ref AWS::NoValue, multi-line UserData and inline IAM policies, and search for TODO: comments. Download the result as main.tf and run terraform validate.
Retain, import and planSet DeletionPolicy: Retain, delete the stack, add import blocks and run terraform plan until it reports no changes.
Expand SAM and macros before converting, so the template shows the resources that actually exist.
When converting CloudFormation YAML to Terraform makes sense
Most hand-written CloudFormation is YAML: it allows comments, the short-form tags are easier to read, and many AWS sample templates are published in YAML. That makes YAML templates the ones teams most often need to bring into a Terraform codebase, whether they're retiring a stack, standardizing on one tool, or reusing an AWS sample template in a Terraform project.
The converter handles the tedious part: turning each tag into an expression, each property into a provider argument, and each section into the right kind of Terraform block. It's also a quick way to learn Terraform if you know CloudFormation well: paste a template you understand and read the HCL side by side.
Once the HCL is ready, the resources themselves still belong to the stack. The migration steps below cover retaining and importing them. If your team works in code rather than templates, the Terraform to CDK TypeScript and Terraform to Pulumi converters take the result further.
Each top-level section of the template has a home in the Terraform configuration.
From YAML tags to HCL expressions
YAML templates usually use the short form of intrinsic functions: !Ref, !GetAtt SessionsTable.Arn and !Sub "sessions-${Stage}". The converter rewrites them as Terraform references and interpolation, such as aws_dynamodb_table.sessions.arn and "sessions-${var.stage}". The full forms, like Fn::GetAtt:, convert the same way.
YAML adds a few things JSON templates don't have. Comments carry over as HCL # comments where they still describe the code. Literal block scalars (|) such as UserData scripts become heredoc strings (<<-EOT). And because YAML can't stack two tags on one value, templates often mix forms, as in Fn::Base64: !Sub |.
Resource properties map to provider arguments, sometimes with a different shape. A DynamoDB TimeToLiveSpecification becomes a ttl block, KeySchema and AttributeDefinitions become hash_key plus attribute blocks, and an IAM role's inline Policies can become a separate policy resource.
Before deleting the old stack, set DeletionPolicy: Retain on its resources and update the stack. Deleting a stack without it removes the resources too.
Short-form tags, block scalars and comments all have a direct HCL counterpart.
YAML syntax to Terraform: quick reference
The YAML-specific constructs you'll meet most often, and what to check after conversion.
CloudFormation YAML syntax and the equivalent Terraform HCL
CloudFormation YAML
Terraform HCL
What to check
!Ref Stage or Ref: Stage
var.stage
Variable names may keep the parameter's original case
!GetAtt Table.Arn
aws_dynamodb_table.table.arn
The dot form and the list form mean the same thing
!Sub "logs-${Stage}"
"logs-${var.stage}"
${AWS::Region} inside the string needs a data source
!Sub ["${Name}-logs", {Name: !Ref App}]
"${var.app}-logs"
The variable map is inlined into the string
!Equals [!Ref Stage, prod]
var.stage == "prod"
Usually stored in locals
Condition: IsProd on a resource
count = local.is_prod ? 1 : 0
References become name[0]
!If [IsProd, x, !Ref AWS::NoValue]
local.is_prod ? x : null
Use null, not an empty string, to omit an argument
| literal block
<<-EOT … EOT
Keeps line breaks, which scripts need
> folded block
A single-line string
Folded blocks join lines with spaces; check the result
# comment
# comment
Kept where it still applies
Transform: AWS::Serverless-2016-10-31
No equivalent
Expand to the processed template first
Key features
Short and full forms understood!Ref and Ref:, !Sub and Fn::Sub:, !GetAtt A.B and the list form all convert the same way.
Conditions become count and ternariesConditions, !If and !Equals become locals, count and conditional expressions, with AWS::NoValue as null.
Block scalars become heredocsMulti-line UserData scripts and inline documents become Terraform heredoc strings, wrapped in base64encode() where the argument needs it.
Comments carried overYAML # comments stay in the HCL wherever they still describe the code, so the reasoning behind a setting isn't lost.
Properties reshaped for the providerStructures that differ between the two, such as DynamoDB key schemas and TTL settings, become the provider's blocks and arguments.
Unsupported parts flaggedTransforms, macros and custom resources get a TODO: comment instead of an invented equivalent.
Works on real templatesOpen .yaml, .yml or .template files up to 500 KB; the page checks them for keys and tokens before sending.
Save as main.tfCopy the HCL or download it as main.tf, ready for terraform validate.
Examples: CloudFormation YAML converted to Terraform
Two YAML patterns that don't translate word for word. Each shows the template and the Terraform it should become. Choose Try this example to load the YAML into the converter; your output may differ in layout.
Optional versioning with !If and AWS::NoValue
In provider version 4 and later, S3 versioning is its own resource, so the condition moves from a property to count on aws_s3_bucket_versioning.
variable "stage" {
type = string
validation {
condition = contains(["dev", "prod"], var.stage)
error_message = "stage must be dev or prod."
}
}
data "aws_caller_identity" "current" {}
locals {
is_prod = var.stage == "prod"
}
resource "aws_s3_bucket" "uploads" {
bucket = "uploads-${data.aws_caller_identity.current.account_id}-${var.stage}"
}
resource "aws_s3_bucket_versioning" "uploads" {
count = local.is_prod ? 1 : 0
bucket = aws_s3_bucket.uploads.id
versioning_configuration {
status = "Enabled"
}
}
A launch template with a multi-line UserData script
The literal block becomes a heredoc, ${Stage} inside the script becomes ${var.stage}, and the comment comes along. Launch templates expect base64, hence base64encode().
CloudFormation (YAML)
Parameters:
Stage:
Type: String
Resources:
WebTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub "web-${Stage}"
LaunchTemplateData:
InstanceType: t3.small
# Set the stage and start the app on boot
UserData:
Fn::Base64: !Sub |
#!/bin/bash
echo "STAGE=${Stage}" >> /etc/environment
systemctl start web
Terraform (HCL)
variable "stage" {
type = string
}
resource "aws_launch_template" "web_template" {
name = "web-${var.stage}"
instance_type = "t3.small"
# Set the stage and start the app on boot
user_data = base64encode(<<-EOT
#!/bin/bash
echo "STAGE=${var.stage}" >> /etc/environment
systemctl start web
EOT
)
}
Tips for better results
Paste valid YAML. Indent with spaces, never tabs. If the template deploys today, it's valid enough to convert.
Check ARN construction.!Sub arn:aws:... strings often have a direct Terraform attribute, such as .arn, which is cleaner than rebuilding the string.
Test each condition branch. Run terraform plan with each value a condition depends on, such as -var stage=dev and -var stage=prod.
Read heredocs carefully. Terraform interpolates ${...} inside heredoc strings. A shell variable like ${HOME} in a script needs escaping as $${HOME}.
Run a plan after import.terraform plan should show no changes for resources you imported.
Common mistakes to avoid
Converting a SAM template as it is.AWS::Serverless::Function expands into a function, a role and sometimes more. Convert the processed template, or those resources won't exist in Terraform.
Replacing AWS::NoValue with an empty string. An empty string is still a value. Use null so Terraform leaves the argument out.
Changing the IAM policy type. Inline Policies on a role match aws_iam_role_policy. If the converted code uses a managed aws_iam_policy and an attachment instead, it no longer matches what you import.
Keeping names the template didn't set. If the converted code adds a name the template never had, it won't match the generated name of the live resource.
Referencing a conditional resource without an index. Once a resource has count, other code must refer to it as aws_s3_bucket_versioning.uploads[0], and only when it exists.
Deleting the stack before retaining. Without DeletionPolicy: Retain, deleting the stack deletes the resources.
Frequently asked questions
How do I convert CloudFormation YAML to Terraform?
Paste the template into the converter above and press Convert to Terraform. Short-form tags become Terraform references and interpolation, Parameters become variables, Conditions become count or conditional expressions, and Outputs become outputs. Then validate the HCL, import the existing resources and check that terraform plan shows no changes.
What happens to !Ref, !Sub and !GetAtt?
They become Terraform expressions. !GetAtt Table.Arn becomes aws_dynamodb_table.table.arn, !Sub becomes string interpolation, and !Ref becomes whichever attribute that resource type returns for Ref, such as an ID, name or ARN. For every intrinsic function and its gotchas, see CloudFormation !Sub, !Ref and !GetAtt to Terraform.
Can I mix short-form tags and Fn:: syntax?
Yes. CloudFormation accepts both in the same template, and so does the converter. You'll often see them combined, as in Fn::Base64: !Sub |, because YAML can't put two tags on one value.
Are YAML comments kept?
Yes, where they still apply. Comments become HCL # comments next to the code they describe. Comments about CloudFormation-only details may be dropped or rewritten.
What does !Ref AWS::NoValue become?
Usually null inside a conditional expression, which tells Terraform to leave the argument out. When a whole block or resource is conditional, it becomes a dynamic block or count instead.
Can I move deployed resources without recreating them?
Yes. Retain the resources in the CloudFormation stack, delete the stack, then import each resource into Terraform with import blocks. Check that terraform plan shows no changes afterwards. The step-by-step guide shows how to import CloudFormation resources into Terraform safely.
What about SAM and Serverless templates?
SAM resources such as AWS::Serverless::Function expand into several CloudFormation resources at deploy time. Convert the processed template to see the real resources you need in Terraform.