For years, adopting an existing resource into Terraform meant the same awkward dance: hand-write a resource block, run terraform import on the CLI, then run a plan and fix the inevitable diff. Terraform 1.5 replaced that with the config-driven import block — a declarative, reviewable, plannable way to bring resources under management, with the bonus that Terraform can write the starter HCL for you. This tutorial walks through it end to end.
- The old way (and its problems)
- The import block
- Generating configuration automatically
- Importing many resources with for_each
- Cleaning up afterward
- When import doesn't scale
- FAQ
The old way (and its problems)
The original terraform import command was imperative and stateful:
# Old: write the resource block by hand first, then:
terraform import aws_instance.web i-0abc123def456
It bound the resource into state but wrote no configuration — you authored every attribute by hand and discovered what you missed only when the next plan proposed changes. It ran outside the plan/apply cycle, so there was nothing to review in a pull request. For more than a handful of resources, it was slow and error-prone.
The import block
The modern approach is a block you write in your configuration:
# imports.tf
import {
to = aws_instance.web
id = "i-0abc123def456"
}
resource "aws_instance" "web" {
# ... your configuration ...
}
Now terraform plan shows the import as a planned action you can review, and terraform apply binds the resource into state. Because the import lives in code, it goes through the same review as any other change.
The shift is from "run a command and hope" to "declare it, plan it, review it, apply it." Import becomes part of your normal workflow instead of a side channel.
Generating configuration automatically
You still need the resource block to go with each import. Terraform can write a first draft for you. Add the import block without a matching resource block, then:
terraform plan -generate-config-out=generated.tf
Terraform reads the live resource and writes a best-effort aws_instance.web block into generated.tf. Review it before applying — the generator tends to emit read-only and default attributes you'll want to remove, and it won't replace hard-coded values with references to your other resources. Treat it as a strong starting point, not the finished article.
Importing many resources with for_each
Import blocks compose with for_each, so you can adopt a set of similar resources at once instead of writing one block each:
locals {
buckets = {
logs = "my-app-logs"
backups = "my-app-backups"
}
}
import {
for_each = local.buckets
to = aws_s3_bucket.this[each.key]
id = each.value
}
resource "aws_s3_bucket" "this" {
for_each = local.buckets
bucket = each.value
}
This keeps bulk imports concise and data-driven, which is far easier to review than dozens of near-identical blocks.
Cleaning up afterward
Import blocks are temporary scaffolding. The recommended flow:
- Add the import blocks (often in a dedicated
imports.tf). - Run
plan, review, thenapplyto bind resources into state. - Confirm a follow-up
planreports no changes — proof your config matches reality. - Delete the import blocks in a separate commit. They've done their job; the resources now live in state and config.
When import doesn't scale
The import block is excellent for a known, bounded set of resources. Its hard limit is the same as the old command's: it imports, it doesn't discover. You must already know the type and ID of every resource. For a whole AWS account with hundreds of resources across dozens of services, hand-authoring import blocks — even with config generation — becomes a multi-day project, and you still have to hunt down every ID first.
That's the gap automated scanners fill. InfraSync discovers what's running across 87+ AWS services with a read-only scan and generates the matching HCL with references already wired up — then opens a GitHub PR. For the full menu of approaches, including when plain import blocks are the right call, see our guide to generating Terraform from AWS.
Too many resources to import by hand?
InfraSync discovers and codifies your whole AWS account in one read-only scan — generated HCL, resolved references, one-click GitHub PR. No ID hunting, no per-resource import blocks.
Start a free scanFAQ
What is the Terraform import block?
The import block is a config-driven way, introduced in Terraform 1.5, to bring existing infrastructure under Terraform management. You declare an import block naming the target resource address and the real resource ID, and Terraform binds it into state on the next apply. It can also generate starter HCL for you.
How is it different from the terraform import command?
The old terraform import command was an imperative, one-off CLI action that bound a resource into state but wrote no HCL. The import block is declarative, lives in your code, can be planned and reviewed, supports generating configuration with -generate-config-out, and works with for_each.
Do I remove import blocks after importing?
Yes. Import blocks are meant to be removed once the import has been applied and the resources are in state. Many teams keep them in a temporary file, apply, confirm a clean plan, then delete the blocks in a follow-up commit.