Terraform to Pulumi TypeScript Converter

Paste Terraform HCL and get a Pulumi program in TypeScript: @pulumi/aws resources, Pulumi config for variables and exported outputs, ready to save as index.ts.

Your codeTerraform (HCL)
Drop the file to load it
Tab indents · Esc, Tab exits
ResultPulumi (TypeScript)
Your Pulumi (TypeScript) will appear hereClick Convert to Pulumi or press Ctrl Enter
index.ts
Don't paste secrets

How to convert Terraform to Pulumi TypeScript

To convert Terraform to Pulumi TypeScript, paste your HCL above and press Convert to Pulumi. Each resource block becomes a new aws.* class, arguments become camelCase properties, variables become pulumi.Config values and outputs become exports. Save it as index.ts, set your config, import existing resources and run pulumi preview.

  1. Add your TerraformPaste HCL into the Terraform (HCL) editor, or open a .tf, .hcl or .txt file up to 500 KB. Convert one file or one module at a time. Sample loads an SNS topic, an email subscription and a CloudWatch alarm.
  2. Let the secret check runVariable defaults and tfvars values sometimes hold real credentials. The page checks for AWS keys, private keys and common tokens before anything is sent.
  3. Convert to PulumiClick Convert to Pulumi or press Ctrl + Enter. The TypeScript streams into the right-hand editor, and you can stop it at any time. One conversion takes up to 60,000 characters.
  4. Drop it into a projectDownload the result as index.ts into a Pulumi TypeScript project (for example one created with pulumi new aws-typescript). Set each config key the code reads, such as pulumi config set alert_email you@example.com, and check any TODO: comments.
  5. Import, then previewImport resources that already exist so Pulumi manages them instead of creating copies, then run pulumi preview. It should show no creates or replacements for resources you imported.
Four-step workflow for converting Terraform to Pulumi TypeScript: paste HCL, convert resource blocks into @pulumi/aws classes in index.ts, set variables with pulumi config set, then import existing resources and run pulumi preview
Set config and import existing resources before the first pulumi up.

Why move from Terraform to Pulumi TypeScript

Pulumi lets you define the same AWS infrastructure in a general-purpose language. With TypeScript you get real loops and functions instead of count and for_each workarounds, type checking in your editor, npm packages for shared components, and unit tests in the test framework you already use.

The resources don't change much. Pulumi's AWS provider is built from the Terraform AWS provider, so resource types and arguments line up closely: aws_sns_topic is aws.sns.Topic, alarm_actions is alarmActions. That's what makes a line-by-line conversion like this one practical, and why most of the review effort goes into the parts that differ: config, outputs and resource options.

Use this page for single files, modules and snippets, or to see how a Terraform pattern looks in Pulumi before you commit to a migration. For a whole repository, Pulumi's own pulumi convert --from terraform converts a project in one command. Coming from CloudFormation instead? Convert the template with the CloudFormation YAML to Terraform converter first, then bring the HCL here.

Table of Terraform CLI commands and Pulumi equivalents: terraform plan to pulumi preview, apply to pulumi up, destroy to pulumi destroy, import to pulumi import, workspace to pulumi stack, tfvars to pulumi config set, and terraform output to pulumi stack output
The day-to-day workflow maps closely, so the switch is mostly about the code.

How Terraform maps to a Pulumi program

Most resources map one to one. resource "aws_sns_topic" "alerts" becomes new aws.sns.Topic("alerts", { ... }), where the Terraform name becomes the Pulumi logical name. Arguments become camelCase properties, variable blocks become pulumi.Config reads, locals become ordinary constants, and output blocks become export const statements.

The main new concept is Output<T>. Values such as topic.arn aren't known until deployment, so you pass them straight into other resources, and build strings from them with pulumi.interpolate or .apply(). The converter uses these where Terraform used interpolation.

Terraform's lifecycle settings and meta-arguments become resource options, passed as a third argument: depends_on becomes dependsOn, prevent_destroy becomes protect, ignore_changes becomes ignoreChanges, and a provider alias becomes an explicit aws.Provider.

Converting the code doesn't adopt your deployed resources. Import them into the Pulumi stack first, or pulumi up will try to create duplicates.

Side-by-side comparison of a Terraform aws_sns_topic resource block and the equivalent Pulumi TypeScript aws.sns.Topic class, showing snake_case alarm_actions becoming camelCase alarmActions and a resource reference becoming an Output value
Resource type, name, arguments and references all have a direct counterpart.

Terraform vs Pulumi TypeScript: quick reference

The Terraform constructs you'll convert most often and their Pulumi equivalents.

Terraform HCL constructs and their Pulumi TypeScript equivalents
ConceptTerraformPulumi TypeScript
Resourceresource "aws_sns_topic" "alerts"new aws.sns.Topic("alerts", { … })
Argument namesalarm_actionsalarmActions
Input variablevariable "alert_email"config.require("alert_email") or config.get()
Sensitive variablesensitive = trueconfig.requireSecret() and pulumi config set --secret
Localslocals { … }Plain const values
Outputoutput "topic_arn"export const topicArn = topic.arn
Data sourcedata "aws_caller_identity" "current"aws.getCallerIdentityOutput()
String with references"arn-${aws_sns_topic.alerts.arn}"pulumi.interpolate`arn-${alerts.arn}`
count / for_eachfor_each = toset(var.names)An if statement or a for…of loop
depends_ondepends_on = [aws_iam_role.x]{ dependsOn: [role] }
prevent_destroylifecycle { prevent_destroy = true }{ protect: true }
ignore_changeslifecycle { ignore_changes = [tags] }{ ignoreChanges: ["tags"] }
Provider aliasprovider = aws.use1{ provider: new aws.Provider("use1", { region }) }
Modulemodule "vpc" { … }A ComponentResource class or a function
StateBackend (S3, Terraform Cloud)Stack state (Pulumi Cloud or a self-managed backend such as S3)

Key features

  • Resources become @pulumi/aws classesEach resource block becomes a new aws.* class with camelCase properties, and explicit names such as name = "ops-alerts" are kept.
  • Variables and outputs carried overvariable blocks become pulumi.Config reads and output blocks become export const statements.
  • References stay typedAttributes such as .arn pass straight between resources as Output values, with pulumi.interpolate where Terraform built strings.
  • Loops and conditions in TypeScriptcount and for_each become ordinary if statements and loops.
  • Lifecycle rules become resource optionsdepends_on, prevent_destroy, ignore_changes and provider aliases map to dependsOn, protect, ignoreChanges and provider.
  • Gaps flagged, not guessedProvisioners, backend settings and anything else without a Pulumi equivalent get a TODO: comment.
  • Save as index.tsCopy the program or download it as index.ts. The HCL is checked for keys and tokens in your browser before it's sent.

Examples: Terraform converted to Pulumi TypeScript

Three Terraform patterns and the Pulumi TypeScript they should become. Choose Try this example to load the HCL into the converter; your output may be formatted differently.

for_each becomes a loop

A list variable becomes a config value with the same default, and for_each becomes a plain for…of loop. Each queue needs a unique logical name.

Terraform (HCL)
variable "queues" {
  type    = list(string)
  default = ["orders", "payments"]
}

resource "aws_sqs_queue" "work" {
  for_each                   = toset(var.queues)
  name                       = "${each.key}-work"
  visibility_timeout_seconds = 60
}
Pulumi (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const queues = config.getObject<string[]>("queues") ?? ["orders", "payments"];

const work: Record<string, aws.sqs.Queue> = {};
for (const name of queues) {
  work[name] = new aws.sqs.Queue(`work-${name}`, {
    name: `${name}-work`,
    visibilityTimeoutSeconds: 60,
  });
}

A data source, lifecycle rule and output

The data source becomes getCallerIdentityOutput(), the interpolated name uses pulumi.interpolate, and prevent_destroy becomes the protect option.

Terraform (HCL)
data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "audit" {
  bucket = "audit-logs-${data.aws_caller_identity.current.account_id}"

  lifecycle {
    prevent_destroy = true
  }
}

output "audit_bucket_arn" {
  value = aws_s3_bucket.audit.arn
}
Pulumi (TypeScript)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const current = aws.getCallerIdentityOutput();

const audit = new aws.s3.Bucket("audit", {
  bucket: pulumi.interpolate`audit-logs-${current.accountId}`,
}, { protect: true });

export const auditBucketArn = audit.arn;

A second region with a provider alias

CloudFront needs its certificate in us-east-1. The aliased provider becomes an explicit aws.Provider passed as a resource option.

Terraform (HCL)
provider "aws" {
  alias  = "use1"
  region = "us-east-1"
}

resource "aws_acm_certificate" "site" {
  provider          = aws.use1
  domain_name       = "example.com"
  validation_method = "DNS"
}
Pulumi (TypeScript)
import * as aws from "@pulumi/aws";

const use1 = new aws.Provider("use1", { region: "us-east-1" });

const site = new aws.acm.Certificate("site", {
  domainName: "example.com",
  validationMethod: "DNS",
}, { provider: use1 });

Tips for better results

  • Set names explicitly if they matter. Pulumi adds a random suffix to physical names by default. Keep a fixed name, such as name: "ops-alerts", when other systems depend on it, and always when you import an existing resource.
  • Use the config keys the code reads. The converted program may keep Terraform-style names like alert_email. Set exactly those keys with pulumi config set, or rename them in the code first.
  • Set the region once. A default provider "aws" region becomes stack config: pulumi config set aws:region us-east-1.
  • Preview before deploying. Run pulumi preview and check it only creates or imports what you expect.
  • Convert one module at a time. A Terraform module maps naturally to a Pulumi ComponentResource.

Common mistakes to avoid

  • Running pulumi up before importing. Pulumi doesn't know about resources Terraform created, so it creates new ones, or fails on names that already exist.
  • Letting both tools manage a resource. After importing into Pulumi, remove the resource from Terraform with terraform state rm so a later terraform apply doesn't fight Pulumi.
  • Building strings with plain template literals. `arn-${topic.arn}` gives you a warning message instead of the ARN. Use pulumi.interpolate or .apply().
  • Dropping a name to "let Pulumi handle it". Without an explicit name, Pulumi generates one with a random suffix, so an imported resource no longer matches and gets replaced.
  • Storing secrets as plain config. Use pulumi config set --secret and config.requireSecret() for anything that was a sensitive variable.
  • Reusing a logical name in a loop. Every Pulumi resource needs a unique logical name, so include the loop key, as in `work-${name}`.

Frequently asked questions

How do I convert Terraform to Pulumi TypeScript?

Paste your HCL into the converter at the top of this page and press Convert to Pulumi. Resource blocks become @pulumi/aws classes, variables become pulumi.Config values and outputs become exports. Save the result as index.ts in a Pulumi TypeScript project, set your config, import existing resources and run pulumi preview.

Does Pulumi have an official Terraform converter?

Yes. Pulumi's CLI includes pulumi convert --from terraform, which converts whole Terraform projects. This tool is a quick alternative for individual files, modules and snippets.

Will my Terraform-managed resources move to Pulumi?

Not by converting code. Import them into your Pulumi stack with pulumi import or the import resource option, as described in Pulumi's Terraform migration guide, then remove them from Terraform state so only one tool manages each resource. The guide to migrate a Terraform stack to Pulumi TypeScript covers imports and the state handoff.

What is Output<T> and why does the code use it?

Resource attributes such as ARNs are only known after deployment, so Pulumi wraps them in Output values. You can pass them directly to other resources, and use pulumi.interpolate or .apply() when you need to build a string from them.

What happens to count and for_each?

They become TypeScript: an if statement for a conditional resource, and a for…of loop or map() for repeated ones. Each resource created in a loop needs its own logical name.

How are variables and secrets handled?

Variables become reads from pulumi.Config, such as config.require("alert_email"), and you set values per stack with pulumi config set. Sensitive values should use pulumi config set --secret and config.requireSecret().

Why do my resource names have random suffixes?

Pulumi auto-names resources by adding a random suffix, which lets it replace resources without name clashes. Set an explicit name property when you need an exact physical name.

Is it free?

Yes. Each conversion takes up to 60,000 characters. 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. Your code is sent to an AI model hosted on NVIDIA's API only to convert it, and ChatWithCloud doesn't store it. For the full picture, see whether it's safe to paste AWS code into an AI converter. The guide to ChatWithCloud free converter limits shows how far they stretch for a real migration.

Related tools and next steps