What is the declarative `import` block (Terraform 1.5+), and how does it differ from the `terraform import` CLI command?
Quick Answer
Since Terraform 1.5, a declarative `import` block lets you specify import operations directly in configuration (`import { to = aws_instance.web; id = "i-0abcdef" }`), which `terraform plan` then previews like any other change before you `apply` it — giving you a reviewable diff of exactly what will be imported. The older `terraform import <address> <id>` CLI command performs the same state operation immediately and imperatively, with no plan preview, and isn't recorded anywhere in version control, so a teammate re-running the configuration has no record that an import ever happened. The `import` block is preferred for anything checked into shared configuration, especially bulk/scripted imports; the CLI command remains useful for quick, one-off, exploratory imports.
Detailed Answer
Importing existing, unmanaged infrastructure into Terraform is a common real-world task — a resource created by hand before Terraform existed, or created by another tool — and Terraform now offers two distinct ways to do it, with meaningfully different guarantees.
The CLI command (older, imperative)
terraform import aws_instance.web i-0abcdef1234567890
This immediately writes the mapping into state — there's no preview step, and the operation happens the moment you run it. It requires a matching resource "aws_instance" "web" { ... } block to already exist in configuration (import only populates state, it doesn't write .tf code for you). Nothing about this action is recorded in version control — six months later, nobody looking at the codebase can tell an import ever happened, or when.
The declarative import block (Terraform 1.5+)
import {
to = aws_instance.web
id = "i-0abcdef1234567890"
}
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}
Run terraform plan, and Terraform shows you exactly what importing this resource will do — including whether your resource block's configuration actually matches the real object's current attributes (a common gotcha: importing an object whose real settings don't match what you wrote in .tf, which would otherwise cause an unexpected diff/update on the very next apply). Only on terraform apply does the import actually happen, same as any other reviewable change.
Why the difference matters
- Reviewability. The
importblock turns "bring this resource under management" into a normal, diffable part of the plan/apply workflow, instead of a side-channel operation invisible toplan. - Reproducibility and bulk imports. Because
importblocks are ordinary configuration, they can be generated programmatically (e.g., from a script enumerating hundreds of existing resources) and committed, letting a large migration-to-Terraform effort be reviewed as a PR rather than run as hundreds of manual CLI invocations. - Configuration generation. Combined with
terraform plan -generate-config-out=generated.tf, theimportblock workflow can even scaffold the matchingresourceblock's configuration for you from the real object's current attributes — significantly reducing the manual work of writing.tfcode that exactly matches an existing resource.
When the CLI command is still fine
For a genuinely one-off, exploratory import during local development — "let me quickly check what this looks like" — the CLI command remains simpler. For anything destined for shared, reviewed, or repeated configuration, the import block is the safer default.