Integrations

Terraform Provider

Manage projects, environments, configs, tags, targeting rules, segments, and webhooks as code with the ConfigDirector Terraform provider

Introduction

The ConfigDirector Terraform provider allows you to manage projects, environments, configs, tags, targeting rules, segments, and webhooks via Terraform and OpenTofu.

To make the same changes from an AI coding assistant instead, see the MCP server.

Refer to the official ConfigDirector Terraform provider documentation for a full guide on all the features available in the provider.

Example

Here is a minimal example on how to use the provider:

main.tf
terraform {
  required_providers {
    configdirector = {
      source  = "ConfigDirector/configdirector"
      version = "~> 0.6"
    }
  }
}

provider "configdirector" {}

resource "configdirector_project" "example" {
  name = "Targeting Rules Example"
  slug = "targeting-rules-example"
}

resource "configdirector_config" "beta_features" {
  project_id    = configdirector_project.example.id
  key           = "beta-features"
  role          = "flag"
  type          = "boolean"
  initial_value = false
}

resource "configdirector_config_targeting_rules" "beta_features_test" {
  project_id       = configdirector_project.example.id
  config_key       = configdirector_config.beta_features.key
  environment_slug = "test"
  default_value    = "false"

  rules = [
    {
      id     = provider::configdirector::rule_id("beta-features-test-named-testers")
      type   = "conditional"
      order  = 0
      target = "value"
      value  = true
      conditions = [
        {
          id           = provider::configdirector::rule_id("beta-features-test-named-testers-identifier")
          attribute    = "identifier"
          operator     = "is one of"
          targetType   = "text"
          targetValues = ["user-101", "user-202"]
        }
      ]
    }
  ]
}

resource "configdirector_config_targeting_rules" "beta_features_production" {
  project_id       = configdirector_project.example.id
  config_key       = configdirector_config.beta_features.key
  environment_slug = "production"
  default_value    = "false"

  rules = [
    {
      id     = provider::configdirector::rule_id("beta-features-production-rollout")
      type   = "percentage"
      order  = 0
      target = "percentage"
      percentages = [
        {
          id         = provider::configdirector::rule_id("beta-features-production-rollout-on")
          percentage = 10
          value      = true
        },
        {
          id         = provider::configdirector::rule_id("beta-features-production-rollout-off")
          percentage = 90
          value      = false
        },
      ]
    }
  ]
}

A condition can also test the context kind: set attribute to "kind", targetType to "text", and operator to "equals", "does NOT equal", "is one of", or "is NOT one of", with context kinds as the target values. A context sent without a context kind is tested as user. For example, this condition matches only devices:

{
  id           = provider::configdirector::rule_id("beta-features-test-devices-kind")
  attribute    = "kind"
  operator     = "equals"
  targetType   = "text"
  targetValues = ["device"]
}

Segments

The configdirector_segment resource manages a segment, and is available from version 0.3.0 of the provider. groups holds the project definition as a list of condition groups, each a list of attribute conditions in the same shape as in targeting rules. overrides maps an environment id to that environment's own groups.

The resource owns every override of the segment: leaving overrides out, or removing an environment from it, removes that override. groups and overrides are sent as written and never read back, the same as the rules of configdirector_config_targeting_rules, so changes made outside Terraform do not show as drift.

A targeting rule uses the segment through a segment condition with kind = "segment", an operator of "in" or "not in", and the segment's id as segmentId. Continuing the example above:

main.tf
resource "configdirector_environment" "staging" {
  project_id = configdirector_project.example.id
  name       = "Staging"
  slug       = "staging"
  color      = "purple"
  live       = false
}

resource "configdirector_segment" "beta_testers" {
  project_id = configdirector_project.example.id
  key        = "beta-testers"
  name       = "Beta testers"

  groups = [
    [
      {
        id           = provider::configdirector::rule_id("beta-testers-acme-email")
        attribute    = "traits"
        trait        = "/email"
        operator     = "ends with any of"
        targetType   = "text"
        targetValues = ["@acme.com"]
      }
    ],
    [
      {
        id           = provider::configdirector::rule_id("beta-testers-opted-in")
        attribute    = "traits"
        trait        = "/betaOptIn"
        operator     = "equals"
        targetType   = "boolean"
        targetValues = ["true"]
      },
      {
        id           = provider::configdirector::rule_id("beta-testers-app-version")
        attribute    = "appVersion"
        operator     = ">="
        targetType   = "semver"
        targetValues = ["2.0.0"]
      }
    ]
  ]

  overrides = {
    (configdirector_environment.staging.id) = [
      [
        {
          id           = provider::configdirector::rule_id("beta-testers-staging-named-users")
          attribute    = "identifier"
          operator     = "is one of"
          targetType   = "text"
          targetValues = ["qa-user-1", "qa-user-2"]
        }
      ]
    ]
  }
}

resource "configdirector_config" "new_checkout_flow" {
  project_id    = configdirector_project.example.id
  key           = "new-checkout-flow"
  role          = "flag"
  type          = "boolean"
  initial_value = false
}

resource "configdirector_config_targeting_rules" "new_checkout_flow_production" {
  project_id       = configdirector_project.example.id
  config_key       = configdirector_config.new_checkout_flow.key
  environment_slug = "production"
  default_value    = "false"

  rules = [
    {
      id     = provider::configdirector::rule_id("new-checkout-flow-production-beta-testers")
      type   = "conditional"
      order  = 0
      target = "value"
      value  = true
      conditions = [
        {
          id        = provider::configdirector::rule_id("new-checkout-flow-production-beta-testers-segment")
          kind      = "segment"
          operator  = "in"
          segmentId = configdirector_segment.beta_testers.id
        }
      ]
    }
  ]
}

An existing segment is imported by the project's id or slug and the segment's key, for example terraform import configdirector_segment.beta_testers targeting-rules-example/beta-testers. The groups and overrides cannot be read back, so the first apply after an import sends whatever the configuration declares.

Config tags

Declare config assignments by name with tags on configdirector_config. A managed tag's .name reference makes Terraform or OpenTofu wait for that tag's creation or rename:

main.tf
resource "configdirector_tag" "payments" {
  project_id = configdirector_project.example.id
  name       = "Payments / EU & US"
}

resource "configdirector_config" "checkout" {
  project_id    = configdirector_project.example.id
  key           = "checkout-enabled"
  role          = "flag"
  type          = "boolean"
  initial_value = false
  tags          = [configdirector_tag.payments.name]
}

You can also assign existing literal names, such as tags = ["Payments / EU & US"]. Names are resolved within the config's project using the API's trimming and case-equivalent matching. Spaces, slashes, and punctuation are literal; do not URL-encode them. An unknown name fails with a diagnostic naming the tag and project. Assignments never create tags implicitly. Unknown .name references stay unknown during planning until the tag is available.

ConfigurationEffect
Omit tags, or use nullObserve names and preserve remote assignments
tags = []Clear every assignment and manage an empty selection
A nonempty set of namesManage that selection and correct assignment drift

Omission preserves assignments after import and during unrelated updates. Removing tags relinquishes ownership without clearing the selection. Import a config with <project_id_or_slug>/<key>, for example terraform import configdirector_config.checkout targeting-rules-example/checkout-enabled or the same command with tofu. After refresh, omit tags to preserve assignments or declare a set to take ownership.

Assignments are unordered and limited to 20 distinct project tags. Equivalent spellings share one stable ID; state retains configured spelling while the assigned tag IDs are unchanged, so casing and order alone do not cause repeated drift. A shared tag deletion detaches its assignments, and a declared name that no longer exists fails on the next write. Tags organize configs without affecting evaluation.

Name resolution requires tags:read. Creating a tagged config needs configs:create, and changing its tags later needs config-settings:update. Provider updates also read the config back, so include configs:read for that step. Omitted and empty selections need no catalog lookup. The tag resource separately needs tags:create, tags:update, and tags:delete for the corresponding catalog operations, plus tags:read for refresh. Missing scopes produce errors instead of silently skipping assignments.

data.configdirector_config.<name>.tags and each config in data.configdirector_configs.<name>.configs expose current assigned names under configs:read alone. No standalone tag lookup data source is required. The resource and data-source configuration is the same in Terraform and OpenTofu.

Assignment support can deploy once the catalog and assignment APIs are available on every API instance. It does not depend on config table filters or dashboard feature flags.

Webhooks

The configdirector_webhook resource manages a webhook, and is available from version 0.5.0 of the provider. Pass its secret to your receiver so it can verify each request:

main.tf
resource "configdirector_webhook" "ci" {
  name        = "CI pipeline"
  url         = "https://example.com/configdirector-webhooks"
  project_ids = [configdirector_project.example.id]
  headers = {
    Authorization = "Bearer ${var.ci_token}"
  }
}

module "ci_receiver" {
  source         = "./ci-receiver"
  signing_secret = configdirector_webhook.ci.secret
}

A webhook receives every change event type unless event_types lists some, and no alert events unless alert_kinds lists some, the same defaults as the dashboard's form. Set event_types = [] for a webhook that receives only alert events. Leave project_ids, environment_ids, and tag_ids empty to let everything through; the filters explain what each one lets through.

secret and headers are sensitive, so no plan shows them. The secret stays the same across updates. After a rotation on the webhook's page, the next refresh picks up the new secret and the next apply passes it on to your receiver. ConfigDirector never shows a header value again once it is saved, so Terraform notices a header added or removed outside Terraform but not a value changed there.

If ConfigDirector disables the webhook after repeated failures, the next apply enables it again while enabled is true, its default.

Import a webhook with its ID, for example terraform import configdirector_webhook.ci 01a18564-96d6-7026-bb0e-aab59053d921 or the same command with tofu. The first apply after an import writes the header values the configuration declares. The API token needs the webhooks:manage permission, which only owners and admins can grant.

OpenTofu

The ConfigDirector provider is also available in the OpenTofu registry.