Your infrastructure estate probably features infrastructure resources that were created outside of your infrastructure-as-code (IaC) process. Luckily, if you are using OpenTofu, you can easily import these resources by either using the CLI or the import block inside your configuration.
In this article, we will cover what OpenTofu import is and when to use it, how to import a resource using the CLI and the import block, best practices for OpenTofu import, and how to manage imports at scale using Spacelift.
- OpenTofu import brings existing infrastructure under OpenTofu management by recording it in your state file, without changing the resource.
- You can import in two ways: The tofu import CLI command handles one resource at a time and writes straight to state, with no preview. An import block is reviewed in tofu plan and executed in tofu apply.
- The tofu import CLI command handles one resource at a time and writes straight to state, with no preview.
- An import block is reviewed in tofu plan and executed in tofu apply.
- Use the import block. It’s the recommended method, supports for_each (OpenTofu 1.7+) and the identity argument (OpenTofu 1.12+), and leaves a record in code.
- tofu plan -generate-config-out can write resource configuration for you, but it’s experimental and doesn’t work with for_each.
- Find each resource’s import ID in the “Import” section of its OpenTofu Registry page.
What is OpenTofu import and when to use it?
With OpenTofu import, you bind an infrastructure resource in your provider to an OpenTofu resource. If the import is successful, your state file will contain a record of that object’s attributes, and you’ll be able to manage its lifecycle through OpenTofu, as you would do with any other resource created directly through it.
You will need to use the OpenTofu import whenever infrastructure exists outside of your OpenTofu configuration. Here are the most common use cases:
- You have created infrastructure manually or with custom scripts.
- You want to migrate from other tools like CloudFormation, Pulumi, Crossplane, or Azure Bicep.
- State recovery that occurs through accidental deletes or corruption
- Splitting the state when you have a large stack, and you need to move objects to smaller configurations
- Emergency changes performed manually
OpenTofu provides two mechanisms for implementing a resource import:
Using the tofu import CLI command
Using the tofu import CLI command was the original approach, inherited from Terraform. If you use this command with an OpenTofu resource address and an ID from your cloud provider, it will write to your state file immediately. However, you will have not plan, preview, or approval step.
Using the import block
The import block is a configuration-driven import, and you write it in your OpenTofu configurations. It is evaluated during tofu plan, showing you exactly what will happen, and the import itself happens during tofu apply.
The import block has been available since OpenTofu 1.6. It gained for_each support in OpenTofu 1.7, and it gained the identity argument in OpenTofu 1.12.
Note: The identity argument is useful for resources whose identifier is some sort of compound key.
Before doing an OpenTofu import, you’ll need to ensure you use an OpenTofu version that supports the capabilities you are looking for in the import.
You will need to set up your provider, a remote backend if you are working in a production setup, and read access to the resources. You must also ensure that the resource type supports import (this information is in the documentation).
How to find the right import ID for your resource
Some resources will be simply imported by their IDs, like an EC2 instance. Others, like an aws_security_group_rule, will be a compound key (in this case, you will need the security group ID, the rule type, protocol, ports, and source, all joined by underscores).
In any case, you should always check the “Import” section of a resource at the bottom of the relevant page in the provider documentation in the OpenTofu registry. For your resources, you can also use the cloud provider CLI to find their actual IDs and other parameters without navigating the console.
If you want to see how to use terraform import (tofu import works almost the same with a binary swap, and some extra parameters), you can check out this video:
Method 1: Importing a resource with the tofu import CLI command
Let’s now review how to use the CLI approach to import a resource in OpenTofu.
Basic syntax
To find out what parameters you can use, you can run:
The ADDR is any valid resource address, which means that you can use OpenTofu import for resources in the root module, inside the module, or even in specific instances of resources if you are using count or for_each. As mentioned before, the ID is simply the provider-specific import ID.
Step-by-step example
Now let’s take a look at an example. For this one, I will create an S3 bucket using the CLI:
After the bucket is created successfully, let’s create the OpenTofu configuration that will import this resource:
Now I’ll initialize the configuration with tofu init. As the S3 import docs point out, you need to use the bucket name as the ID.
Let’s import the resource now:
To check the import, we can use tofu state list. For example:
At the same time, we can run a tofu plan to see if we need to make any changes to our configuration:
CLI import flags worth knowing
When running tofu import --help, you’ll get a list of all the supported flags you can use with the import command. Keep in mind that all of these flags are optional.
The most important ones are:
- -config=path: This is the path to the directory that contains the OpenTofu configuration that configures the provider (defaults to the working directory).
- -var ‘name=value’ and -var-file=file.tfvars: This helps you set input variables.
- -no-color: Useful in CI logs
- -input=false: Stops OpenTofu from prompting interactively
Importing into modules, count, and for_each resource instances via CLI
Any resource address is valid, so let’s review how to import into modules, count, and for_each resources.
Let’s suppose you have a module called networking, and you have an aws_vpc in it called main.
The command would be similar to:
Now, let’s suppose you have an aws_vpc resource called main with a count set. The import command would look like:
Similarly, let’s suppose you have the same aws_vpc resource, but this time with a for_each set. The import command would look like:
On some operating systems, you might need to escape the double quotes in the resource name using a backslash, as we have done here:
Limitations of CLI import
Using CLI import has real drawbacks, which is it is recommended to use an import block instead.
Here are some of the key limitations:
- You have one object per invocation, so if you need to import 50 resources, you will need to run the command 50 times.
- There is no preview, so this command actually mutates the state directly. If you target the wrong address, you will need to remove that resource from the state, and then add the correct one.
- There is no configuration generation, so you’ll need to write every resource block manually (you could potentially use tofu state show to reverse-engineer it).
- There is no record in code, so you won’t know in six months if a resource was imported or created.
- It aligns poorly with CI/CD pipelines because importing a resource into your OpenTofu code requires modification of your pipeline to add the import step. You will then need to remove it after a single run.
The import block is the recommended way to import a resource, and for good reason. The import block fixes all the issues above, and because every block is code in your configuration, it gets reviewed and planned and is easy to see in history.
It works perfectly with any kind of resource address, so you can still use modules, counts, and for_each.
Basic syntax
The import block can live in any HCL file in your configuration, but a best practice is to have a dedicated imports.tf file, or to place the block next to the resource you will import into.
The to argument
The to argument is the resource you will import into. So for our bucket example, that will be the bucket resource address, so in our case it is: aws_s3_bucket.my_bucket.
An import block is active only when OpenTofu is not already tracking an object at the address in to. So after you are finished with the import, you can keep the block for historical record, or delete it if you want.
The id argument vs the newer identity argument
The id argument and the identity one help you track what object you want to import in OpenTofu. The first is the legacy approach: a single string in whatever format the provider expects, while the latter (added in OpenTofu 1.12) specifies the resource using an object whose attributes come from the resource type’s identity schema (in the documentation).
For example, importing the same S3 bucket would look like this:
The provider argument
If you are using multiple providers in your OpenTofu configuration with aliases, you can easily leverage the provider meta-argument to specify which provider should be used when importing a resource.
Let’s take a look at a simple example:
This will tell the import block it should use the configuration from the import provider to check for that bucket and import it using those credentials.
Step-by-step example
For the step-by-step example, we can reuse one of the code samples from above. Let’s go for the most straightforward one first:
First, you’ll need to ensure that you have removed the previous configuration, and the easiest way to do that is to actually remove the .terraform directory, the .terraform.lock.hcl file, the terraform.tfstate file, and also the terraform.tfstate.backup file.
Of course, if you are starting from scratch, you don’t need to do any of that. Either way, you will first need to run tofu init, and then you can run tofu plan to see how the import will occur:
As you can see, the plan says there is one resource to import, and it shows you the parameters of that resource. If you run tofu apply, the resource will be imported:
Now the import is complete, and if you run tofu apply one more time without removing the import block, this will have no impact:
Importing multiple resources with for_each in an import block
Let’s explore how to import multiple resources using for_each. For that, I will create a second bucket using the AWS CLI:
Now, let’s look at what the configuration would look like:
I’ve declared a variable containing the bucket_names, a resource with for_each based on that variable, and in the import block, I’ve added the for_each meta-argument based on that same variable.
When I’m referencing the resource address in the to argument, I’m also using each.value in it to tell OpenTofu that I want an instance of that resource.
Next, you will need to go through the tofu init/plan/apply workflow, and the resources will be imported:
Why are import blocks the recommended approach?
Import blocks are the recommended approach because they address all the limitations of the tofu import CLI command. With the import block:
- You have the resource history (you know if it was imported or created by OpenTofu).
- You can import multiple resources with a single block if you are using the for_each argument.
- You have a preview of what is going to happen because you are using tofu plan before making any mutations, making the process becoming CI-friendly.
- You can also generate the configuration automatically, without needing to write the resource code (we will explore this in the next section).
Let’s now look at how to generate the configuration automatically by leveraging the -generate-config-out parameter.
Note: This doesn’t work for resources that use for_each or count, and the feature is still experimental
This is what the configuration looks like:
First, you will need to run tofu init again, and then you will run:
For S3 buckets, you might see something like this:
Remove the bucket_prefix from the generated file and run tofu plan again:
Here are some of the most common import errors you will face:
- Configuration for import target doesn’t exist: This happens because you wrote an import block with no matching resource block, and you didn’t use the -generate-config-out.
- Cannot import non-existent remote object: This happens when the ID of the resource you want to use doesn’t exist (or you simply have a typo). To ensure you specify the correct ID OpenTofu expects, always use the resource’s documentation and look for the import section.
- Resource is already managed by OpenTofu: If you already have something in your state at the same resource address, the import won’t work. You should use tofu state list/show commands and figure out what is happening, and then pick a different address
- Resource does not support import: Some resources don’t have import logic implemented in the provider, and that’s why you should always check the docs before starting to import a resource.
The most important best practice for importing resources in OpenTofu is to use import blocks. The import cli command’s many limitations are solved by simply leveraging the import block instead.
You should always use remote state for your OpenTofu configurations, regardless of your team size. Below are some other best practices:
- Enable state versioning before running tofu import, as you might need to rollback to a previous state version.
- Try to import your resources in small batches, using your CI workflow (It’s possible to review up to 20 resources in a pull request, but not hundreds of them.).
- You should always treat generated configuration as a draft. Remove the null and defaulted attributes before committing that code, and remember that this is still an experimental feature in OpenTofu 1.12.
- Leverage the identity attribute where it is supported. You get better granularity than you would by using the id one.
- Review imports as you would review any other change.
Importing hundreds of resources across multiple accounts involving multiple teams can become cumbersome. Managing a generic CI/CD process for classic OpenTofu operations at scale is error-prone, but adding imports can easily turn it into a nightmare.
You need an infrastructure orchestration platform that provides an audit trail, credentials that don’t live on somebody’s machine, a review step with approvals before mutating state, and policies that ensure imported resources respect your organization’s boundaries.
Spacelift is an infrastructure orchestration platform that supports OpenTofu, Terraform, Terragrunt, Pulumi, CloudFormation, Ansible, and Kubernetes.
For OpenTofu import specifically, you get cloud integrations that issue short-lived credentials per run, an audit trail that helps you understand what has happened, and policy as code based on OPA so you can define how many approvals you need for runs, what kind of resources can be created and what kind of parameters they can have, where to send notifications, and more.
As the import block will flow through your normal GitOps process, you can leverage everything Spacelift offers, making importing resources at scale manageable.
If you want to learn more about how Spacelift can help you with importing resources, book a demo with one of our engineers.
Importing resources into OpenTofu is a standard process when working with infrastructure. Resources are sometimes created outside your normal IaC process, so you need something to help you get that resource into your state. Importing a resource won’t modify it until you apply a configuration that changes it.
You should always use the import block instead of the CLI command because it lets you treat an import operation the same way you treat a create/change/delete operation.
Frequently asked questions
- What is the difference between tofu import and the import block?The tofu import command imports one resource per run and writes it directly to state with no plan or preview. The import block lives in your configuration, so imports show up in tofu plan, run during tofu apply, support for_each, and can generate configuration.
What is the difference between tofu import and the import block?
The tofu import command imports one resource per run and writes it directly to state with no plan or preview. The import block lives in your configuration, so imports show up in tofu plan, run during tofu apply, support for_each, and can generate configuration.
- What is the difference between tofu import and terraform import?Both bind an existing infrastructure object to a resource address in state. OpenTofu inherited the command from Terraform, so tofu import works almost the same way, with a binary swap and a few extra parameters.
What is the difference between tofu import and terraform import?
Both bind an existing infrastructure object to a resource address in state. OpenTofu inherited the command from Terraform, so tofu import works almost the same way, with a binary swap and a few extra parameters.
- Can OpenTofu generate configuration automatically when importing?Yes. Running tofu plan -generate-config-out=<file> with import blocks writes HCL for resources that don’t exist in your configuration yet, but the feature is experimental, doesn’t work with for_each or count, and its output should be treated as a draft.
Can OpenTofu generate configuration automatically when importing?
Yes. Running tofu plan -generate-config-out=<file> with import blocks writes HCL for resources that don’t exist in your configuration yet, but the feature is experimental, doesn’t work with for_each or count, and its output should be treated as a draft.
- Does OpenTofu support importing all resources?No. Import support depends on whether the provider implements it for that resource type, so check the “Import” section of the resource’s page in the OpenTofu Registry before you start.
Does OpenTofu support importing all resources?
No. Import support depends on whether the provider implements it for that resource type, so check the “Import” section of the resource’s page in the OpenTofu Registry before you start.
![How to Import Existing Resources to OpenTofu [Tutorial]](/_next/image?url=%2Fapi%2Fuploads%2Farticles%2F959228ac34584bfc1a7416d4541e12d8.webp&w=1920&q=75)











