CloudFormation JSON to Terraform Converter

Paste a CloudFormation JSON template and get Terraform HCL back: resources, variables and outputs, with Ref, Fn::GetAtt and Fn::Sub rewritten as Terraform references.

Your codeCloudFormation (JSON)
Drop the file to load it
Tab indents · Esc, Tab exits
ResultTerraform (HCL)
Your Terraform (HCL) will appear hereClick Convert to Terraform or press Ctrl Enter
main.tf
Don't paste secrets

How to convert a CloudFormation JSON template to Terraform

To convert CloudFormation JSON to Terraform, paste the template into the editor above and press Convert to Terraform. Each resource becomes a resource block, Parameters become variables, Outputs become outputs, and intrinsic functions become HCL expressions. Then run terraform validate and import the live resources before you apply anything.

  1. Add the templatePaste the JSON into the CloudFormation (JSON) editor, or open a .json, .template or .txt file up to 500 KB. For a deployed stack, aws cloudformation get-template --stack-name NAME returns the template body. Sample loads an SNS topic that delivers to an SQS queue.
  2. Let the secret check runTemplates sometimes carry real values in parameter defaults. Before anything is sent, the page scans for AWS keys, private keys and common API tokens, and points to any it finds so you can replace them.
  3. Convert to TerraformClick Convert to Terraform or press Ctrl + Enter. The HCL streams into the right-hand editor, and you can stop it at any time. One conversion takes up to 60,000 characters, so split very large templates by nested stack or by service.
  4. Review and validateCheck every Ref and Fn::GetAtt against the attribute it became, and look for TODO: comments. Download the result as main.tf, then run terraform init and terraform validate to catch argument names that don't exist.
  5. Import, then planRetain the resources in CloudFormation, delete the stack, and add an import block for each resource. terraform plan should report no changes before you ever run apply.
Four-step workflow for converting CloudFormation JSON to Terraform: paste the template, convert Fn:: functions to HCL references, run terraform validate, then import resources and confirm the plan shows no changes
Validate the HCL before you import anything. It catches argument names the provider doesn't have.

Why convert a CloudFormation JSON template to Terraform

Teams usually make this move when Terraform becomes the standard for new infrastructure and the older CloudFormation stacks are the odd ones out. JSON templates are common among those older stacks, and they're also what you get from aws cloudformation get-template or from tools that generate CloudFormation, such as troposphere.

Rewriting JSON by hand is slow and error-prone. Every value is wrapped in an Fn:: object, properties are PascalCase while Terraform arguments are snake_case, and some properties become nested blocks or even separate resources. This converter produces a first draft of the whole template in one pass, so your time goes into checking references and importing resources rather than retyping them.

If the JSON came from cdk synth, converting the CDK source with the AWS CDK to Terraform converter usually gives more readable code than converting the synthesized template. And if your templates are in YAML, use the CloudFormation YAML to Terraform converter instead.

Migration path from a CloudFormation stack to Terraform state: set DeletionPolicy Retain, delete the stack while the resources stay in AWS, add Terraform import blocks, then run terraform plan and expect no changes
Converting the template is the first half. Moving ownership of the live resources is the second.

Translating intrinsic functions to Terraform

The heart of this conversion is CloudFormation's intrinsic functions. {"Ref": "OrderQueue"} becomes a resource attribute such as aws_sqs_queue.order_queue.id, Fn::GetAtt becomes an attribute like .arn, and Fn::Sub becomes string interpolation: "order-events-${var.env}". Parameters become variable blocks and Outputs become output blocks.

What Ref returns depends on the resource type: an SNS topic's ARN, but an SQS queue's URL and an S3 bucket's name. The converter has to pick the matching Terraform attribute for each case, so these are the first lines to check. Conditions usually become count expressions, and Mappings become maps in locals.

Converting the template doesn't release the resources from their CloudFormation stack. Retain them in the stack first, then adopt them with Terraform import blocks.

Set "DeletionPolicy": "Retain" on every resource and update the stack before you delete it. Otherwise deleting the stack also deletes the resources you're about to import into Terraform.

Table of CloudFormation JSON intrinsic functions and Terraform equivalents: Ref to var.env, Fn::GetAtt to a resource attribute, Fn::Sub to interpolation, Fn::Join to join(), Fn::FindInMap to a locals lookup and Fn::ImportValue to the aws_cloudformation_export data source
Every Fn:: wrapper in the JSON becomes a plain expression in HCL.

CloudFormation JSON to Terraform: quick reference

How the parts of a JSON template usually translate. The right-hand column says what to check in the converted code.

CloudFormation JSON constructs and their Terraform equivalents
CloudFormation JSONTerraform HCLWhat to check
{"Ref": "Env"} (parameter)var.envParameters become variable blocks; AllowedValues can become a validation block
{"Ref": "Queue"} (resource).id, .arn or .nameDepends on what Ref returns for that type: an SQS queue URL is .id, an SNS topic ARN is .arn
"Fn::GetAtt"aws_sqs_queue.queue.arnAttribute names change case and sometimes name
"Fn::Sub""logs-${var.env}"Pseudo parameters inside the string need data sources
"Fn::Join", "Fn::Split", "Fn::Select"join(), split(), list[0]Often replaceable by a direct attribute, such as .arn
"Fn::If" with "Conditions"count = local.is_prod ? 1 : 0 or a ? b : cResources with count are referenced as name[0]
"Fn::FindInMap" with "Mappings"local.map[key].attrKeys keep their original spelling
"Fn::ImportValue"data "aws_cloudformation_export"Works only while the exporting stack exists
"Fn::GetAZs"data "aws_availability_zones"Returns the names list; filter by state if needed
"Fn::Base64"base64encode()aws_launch_template expects encoded user_data; aws_instance takes plain text
"AWS::AccountId"data.aws_caller_identity.current.account_idDeclare the data source once
"DependsOn"depends_onOften unnecessary: references already order resources
"DeletionPolicy": "Retain"lifecycle { prevent_destroy = true }Not the same: it blocks a destroy instead of leaving the resource behind

Key features

  • Intrinsic functions become HCLRef, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::If and Fn::FindInMap are rewritten as Terraform references, interpolation and functions.
  • Every template section mappedParameters become variables, Outputs become outputs, and Conditions and Mappings become locals, count and conditional expressions.
  • Properties become provider argumentsPascalCase properties become the matching snake_case arguments, such as VisibilityTimeout to visibility_timeout_seconds, with nested blocks where the provider uses them.
  • Pseudo parameters handledAWS::AccountId, AWS::Region and AWS::Partition become data sources such as aws_caller_identity instead of hard-coded values.
  • Gaps are flaggedCustom resources, macros and properties with no Terraform argument get a TODO: comment rather than a silent guess.
  • Large templates, one filePaste up to 60,000 characters, or open a .json or .template file up to 500 KB.
  • Secret check before sendingParameter defaults and outputs are scanned for AWS keys, private keys and tokens in your browser before the template leaves it.
  • Ready to save as main.tfCopy the HCL or download it as main.tf, then run it through terraform validate.

Examples: CloudFormation JSON converted to Terraform

Two constructs that need more than renaming. Each shows the JSON and the Terraform it should become. Choose Try this example to load the JSON into the converter; your output may be laid out differently.

A conditional resource with Fn::If and Fn::Join

The Condition on the bucket becomes count, AllowedValues becomes a validation rule, and AWS::AccountId comes from a data source.

CloudFormation (JSON)
{
  "Parameters": {
    "Env": { "Type": "String", "AllowedValues": ["dev", "prod"] }
  },
  "Conditions": {
    "IsProd": { "Fn::Equals": [{ "Ref": "Env" }, "prod"] }
  },
  "Resources": {
    "LogsBucket": {
      "Type": "AWS::S3::Bucket",
      "Condition": "IsProd",
      "Properties": {
        "BucketName": {
          "Fn::Join": ["-", ["app-logs", { "Ref": "AWS::AccountId" }]]
        }
      }
    }
  }
}
Terraform (HCL)
variable "env" {
  type = string

  validation {
    condition     = contains(["dev", "prod"], var.env)
    error_message = "env must be dev or prod."
  }
}

data "aws_caller_identity" "current" {}

locals {
  is_prod = var.env == "prod"
}

resource "aws_s3_bucket" "logs_bucket" {
  count  = local.is_prod ? 1 : 0
  bucket = join("-", ["app-logs", data.aws_caller_identity.current.account_id])
}

Mappings and a cross-stack Fn::ImportValue

The mapping becomes a map in locals, and the value exported by another stack is read with the aws_cloudformation_export data source, which keeps working while that stack still exists.

CloudFormation (JSON)
{
  "Parameters": {
    "Env": { "Type": "String", "Default": "dev" }
  },
  "Mappings": {
    "SizeByEnv": {
      "dev": { "Instance": "t3.micro" },
      "prod": { "Instance": "m6i.large" }
    }
  },
  "Resources": {
    "Worker": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-0123456789abcdef0",
        "InstanceType": { "Fn::FindInMap": ["SizeByEnv", { "Ref": "Env" }, "Instance"] },
        "SubnetId": { "Fn::ImportValue": "network-PrivateSubnetA" }
      }
    }
  }
}
Terraform (HCL)
variable "env" {
  type    = string
  default = "dev"
}

locals {
  size_by_env = {
    dev  = { instance = "t3.micro" }
    prod = { instance = "m6i.large" }
  }
}

data "aws_cloudformation_export" "private_subnet_a" {
  name = "network-PrivateSubnetA"
}

resource "aws_instance" "worker" {
  ami           = "ami-0123456789abcdef0"
  instance_type = local.size_by_env[var.env].instance
  subnet_id     = data.aws_cloudformation_export.private_subnet_a.value
}

Tips for better results

  • Expand transforms first. SAM templates (AWS::Serverless-2016-10-31) and macros hide the real resources. Get the processed template with aws cloudformation get-template --template-stage Processed and convert that.
  • Convert nested stacks one at a time. Each nested template maps well to its own Terraform module.
  • Keep the Outputs section. It tells the converter which values other stacks and systems rely on, so they become output blocks.
  • Custom resources don't convert directly. Custom:: types run a Lambda function during deployment. Replace them with a native Terraform resource or a separate step.
  • Plan after importing. Once resources are imported, terraform plan should report no changes. Differences point to arguments the converter set differently from the live resource.

Common mistakes to avoid

  • Trusting every Ref. Ref returns an SQS queue's URL, an SNS topic's ARN and an S3 bucket's name. Check each one maps to the matching attribute.
  • Skipping terraform validate. Converted arguments can look right and still not exist. For example, aws_sqs_queue_policy takes a single queue_url, not a queues list. Validate before you plan.
  • Keeping names the template never set. When a template leaves a name out, CloudFormation generates one. If the converted code adds its own name, it won't match the live resource you import.
  • Deleting the stack without Retain. CloudFormation deletes the resources with the stack unless DeletionPolicy says otherwise.
  • Applying before importing. Without import blocks, terraform apply tries to create a second copy of every resource, or fails on names that already exist.
  • Treating prevent_destroy as Retain. It makes Terraform refuse to destroy the resource; it doesn't leave the resource behind when you remove it from the code.

Frequently asked questions

How do I convert a CloudFormation JSON template to Terraform?

Paste the template into the converter at the top of this page and press Convert to Terraform. It rewrites each resource as a Terraform resource block, turns Parameters and Outputs into variables and outputs, and replaces intrinsic functions with HCL expressions. Then run terraform validate, import the existing resources and check that terraform plan shows no changes.

How do I move a stack's resources from CloudFormation to Terraform?

Set DeletionPolicy: Retain on each resource and update the stack, then delete the stack so CloudFormation lets go without deleting anything. Add import blocks (Terraform 1.5 or later) for each resource and check that terraform plan shows no changes. The step-by-step guide shows how to import CloudFormation resources into Terraform safely.

What happens to Parameters, Conditions, Mappings and Outputs?

Parameters become variable blocks, keeping defaults and, where possible, AllowedValues as validation rules. Conditions become locals used by count or conditional expressions, Mappings become maps in locals, and Outputs become output blocks.

What about Fn::ImportValue and cross-stack references?

An import usually becomes the aws_cloudformation_export data source, which reads the value while the exporting stack still exists. Once that stack moves to Terraform too, replace it with a direct reference, a module output or a data source that looks the resource up. The reference for translating CloudFormation intrinsic functions to Terraform covers Fn::ImportValue too.

Is there an official tool that converts CloudFormation to Terraform?

Neither AWS nor HashiCorp provides a template converter. Terraform 1.5 and later can generate configuration for resources you import, which is a different route: it writes HCL from the live resource rather than from your template, without your parameters, conditions or comments.

Can it convert SAM templates?

It works best on plain CloudFormation. For SAM or other transforms, download the processed template, which contains the expanded resources, and convert that instead.

Does it support YAML templates?

This page expects JSON. For YAML templates with short-form tags like !Ref, use the CloudFormation YAML to Terraform converter.

Is my template stored?

No. The template is sent to an AI model hosted on NVIDIA's API only to produce the conversion, and ChatWithCloud doesn't store or log it. Your latest draft stays in your browser. Replace real passwords and keys in parameter defaults before converting. For the full picture, see whether it's safe to paste AWS code into an AI converter.

Is it free, and how big can the template be?

Yes, it's free. Each conversion accepts up to 60,000 characters, and files up to 500 KB. Without an account you can run 6 conversions a minute and 60 a day; a free account raises that to 12 a minute and 200 a day. The guide to ChatWithCloud free converter limits shows how far they stretch for a real migration.

Related tools and next steps