What is the declarative `import` block (Terraform 1.5+), and how does it differ from the `terraform import` CLI command?

5 minadvancedterraformimportstate

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

  1. Reviewability. The import block turns "bring this resource under management" into a normal, diffable part of the plan/apply workflow, instead of a side-channel operation invisible to plan.
  2. Reproducibility and bulk imports. Because import blocks 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.
  3. Configuration generation. Combined with terraform plan -generate-config-out=generated.tf, the import block workflow can even scaffold the matching resource block's configuration for you from the real object's current attributes — significantly reducing the manual work of writing .tf code 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.

Related Resources