Terraform provider v2.x to v3.0 migration guide
This topic explains how to upgrade your LaunchDarkly Terraform configuration from v2.x to v3.0.
Version 3.0 changes every nested block to a nested attribute, so configurations written for v2 do not parse after you upgrade the provider. You must rewrite them before your first plan against v3. The provider ships the migrate-tf-syntax tool to automate most of the rewrite, and it upgrades your Terraform state automatically on your first apply.
To learn more about v3.0, read the LaunchDarkly Terraform provider release notes.
Here are the changes between versions 2 and 3 of the Terraform provider:
- Blocks become nested attributes
- Single-object attributes use object syntax
- Keyed collections become maps
- Convert your configuration with
migrate-tf-syntax - Finish the migration by hand
- How v3 upgrades your state
Prerequisites
You need the following things to complete this migration:
- Terraform 1.0 or later
- A v2 configuration that applies cleanly, with an empty plan before you start
- A committed copy of your configuration and state, so you can review the upgrade as a diff
Blocks become nested attributes
Version 3.0 rebuilds the provider on the Terraform Plugin Framework. Every nested block becomes a nested attribute. You assign a single nested structure with =, and you wrap a repeated structure in a list of objects.
Here is the same resource in both syntaxes:
This change affects every block in the provider.
Single-object attributes use object syntax
Most blocks become a list of objects. Attributes that hold exactly one object use object syntax instead, a bare { ... } with no surrounding brackets. These attributes are:
client_side_availabilityanddefaultsonlaunchdarkly_feature_flagdefault_client_side_availabilityonlaunchdarkly_projectfallthroughonlaunchdarkly_feature_flag_environmentapproval_settingsonlaunchdarkly_environment, and inside each project environmentsegment_approval_settingsonlaunchdarkly_environmentinstructionsonlaunchdarkly_flag_triggerboolean_defaultsonlaunchdarkly_flag_templates
The migrate-tf-syntax tool emits this form for you:
When you read one of these attributes from a data source, use object access without a list index, for example data.launchdarkly_feature_flag.x.client_side_availability.using_environment_id.
Keyed collections become maps
Four collections whose elements have a natural unique key become maps in v3, so adding, removing, or reordering one entry no longer forces a diff or a destructive plan on its siblings.
The environments attribute on launchdarkly_project moves from an ordered list to a map keyed by the environment key. The key attribute also stays inside each object and equals the map key, so references like launchdarkly_project.example.environments["production"].key keep working. The migrate-tf-syntax tool performs this rewrite for you:
The map is authoritative. Terraform deletes any environment you remove from the map on apply, and a project must have at least one environment. To manage a project in Terraform but its environments in the LaunchDarkly user interface (UI), or with launchdarkly_environment resources, declare your environments and add lifecycle { ignore_changes = [environments] }.
Changing an environment key deletes the environment
An environment’s key serves as the map key. Changing an environment’s key deletes that environment, including its SDK keys and all flag targeting, and then creates a new environment.
Reference an environment by its key instead of by index. A v2 interpolation such as launchdarkly_project.example.environments[0].client_side_id becomes launchdarkly_project.example.environments["production"].client_side_id. The migrate-tf-syntax tool does not rewrite these positional references, because auto-editing arbitrary expressions risks corrupting your configuration. It detects them and prints the exact replacement to make, including the resolved key, so the fix is mechanical. To learn more, read Finish the migration by hand.
Three more collections follow the same key-addressed map pattern, and the tool rewrites all of them:
custom_propertiesonlaunchdarkly_feature_flagbecomes a map keyed by the custom propertykey, for examplecustom_properties = { "my.key" = { name = ..., value = [...] } }.role_attributesonlaunchdarkly_teamandlaunchdarkly_team_memberbecomes a plain map of string lists keyed by the role attribute key, for examplerole_attributes = { myAttribute = ["value1", "value2"] }. This is the same shape thelaunchdarkly_team_role_mappingresource already uses.edgeson the newlaunchdarkly_ai_agent_graphresource is a map keyed by edge key. It is net-new in v3, so no rewrite applies.
Convert your configuration with migrate-tf-syntax
The provider ships migrate-tf-syntax, a deterministic command-line tool that rewrites every affected attribute across a directory of .tf files. It also updates the attributes that v3 removed. For example, it renames policy_statements to inline_roles on launchdarkly_access_token, and it updates references to renamed data source attributes such as client_side_availability on the launchdarkly_project data source. It adds the now-required variations to boolean flags that omitted them.
To convert a configuration directory:
- Download the
migrate-tf-syntaxarchive for your platform from the provider release assets, or run the tool with Go. Replacev3.0.0with the version you are upgrading to. Here is an example:
- Review the dry-run output. The tool prints each file it intends to change.
- Run the same command without
-dry-runto write the changes. Add-recursiveto convert locally vendored modules in the same pass. - Run
terraform fmtto normalize whitespace. - Check any
note:lines the tool printed. For example, an environment whosekeyis a variable or local becomes a parenthesized map key, such as(local.env_key) = { ... }. Confirm the expression is the one you expect. - Update the provider version constraint to
~> 3.0and runterraform plan.
Finish the migration by hand
The tool converts syntax only. Complete these follow-ups yourself:
- Add
variationsby hand only for a flag whosevariation_typeis a non-literal expression, such as a variable or local. The tool cannot resolve those statically, so it warns and skips them. Boolean flags with a literalvariation_typeare handled automatically, and the provider preserves any variationnameordescriptionset outside Terraform when your configuration omits them. - Rewrite
dynamicblocks. Adynamic "variations"block needs aforexpression, for examplevariations = [for v in var.values : { value = v }]. The tool warns with the file and resource address, and it leaves the attribute unchanged. - Upgrade modules sourced from a registry or a git URL. The tool rewrites only files it reaches on disk, so upgrade those modules at their source.
- Rewrite positional references to
launchdarkly_projectenvironments. The tool converts theenvironmentsblock to a map but does not edit index expressions elsewhere in your configuration. It warns on each one with the exact replacement, for exampleenvironments[0]becomesenvironments["production"]andenvironments[*]becomesvalues(...). Apply those edits by hand. - Review projects that pair
lifecycle { ignore_changes = [environments] }with standalonelaunchdarkly_environmentresources. This v2 pattern keeps working in v3. Theignore_changesentry preserves the standalone-managed environments in the authoritative map, so Terraform deletes nothing. If you remove theignore_changesentry, first declare every environment in the project’senvironmentsmap, or the next apply deletes the undeclared ones.
How v3 upgrades your state
The provider includes a state upgrader for every resource whose state shape changed. On your first apply, the provider migrates your state automatically. You do not edit the state file by hand.
Here is a list of state migrations:
launchdarkly_access_token: Movespolicy_statementsintoinline_roles, and discardsexpire.launchdarkly_custom_role: Convertspolicyintopolicy_statements.launchdarkly_feature_flag: Convertsinclude_in_snippetintoclient_side_availability, and re-keyscustom_propertiesinto a map keyed by property key.launchdarkly_project: Convertsinclude_in_snippetintodefault_client_side_availability, re-keys the orderedenvironmentslist into a map keyed by environment key, and converts each environment’sapproval_settingsto an object.launchdarkly_environment: Convertsapproval_settingsandsegment_approval_settingsto objects.launchdarkly_feature_flag_environment: Convertsfallthroughto an object.launchdarkly_flag_trigger: Convertsinstructionsto an object.launchdarkly_flag_templates: Convertsboolean_defaultsto an object.launchdarkly_teamandlaunchdarkly_team_member: Re-keyrole_attributesinto a map of string lists.launchdarkly_metric: Discardsis_active, and renamesrandomization_unitstoanalysis_units, following the LaunchDarkly API’s rename.
Your first plan after upgrading
Expect a non-empty first plan after you upgrade the provider binary. Version 2 stored empty lists where v3 stores null, so diffs such as policy_statements = [] -> null appear once and apply cleanly. You may also see one-time, in-place updates that re-assert values the upgrader normalized away, such as client_side_availability or approval_settings objects that match the LaunchDarkly API defaults. They apply cleanly and do not recur. A few computed attributes show as known after apply on the first plan, and they resolve on apply. No resource is destroyed or recreated. The follow-up plan is empty.
What does not change
- Version 3.0 removes no resources and no data sources. Every v2 resource and data source remains available.
- Authentication is unchanged. The
access_token,oauth_token,api_host,http_timeout, andmax_concurrencyprovider settings keep the same names and behavior.
If you use Crossplane
If you embed this provider through Crossplane’s Upjet, the block-to-attribute change alters the generated custom resource definition (CRD) shape, even though the attribute names do not change. We recommend that you test CRD regeneration against v3 before you upgrade.