Terraform

Manage projects, environments, SDK keys, flags, targeting rules, segments, release templates, triggers and webhooks as code.

Set up the provider

terraform {
  required_providers {
    featflags = { source = "featflags/featflags" }
  }
}

provider "featflags" {
  api_url        = "https://app.featflags.io"   # or FEATFLAGS_API_URL
  change_comment = "Applied by Terraform"       # recorded with every change
}

The provider signs in with an API token (fft_…) in FEATFLAGS_TOKEN. Create one under Settings → API tokens, ideally a service token. A token belongs to one organisation, and Terraform manages that organisation’s projects, so there’s no organisation to configure; the token’s roles decide what Terraform can change.

change_comment goes into history with every change Terraform makes. Environments that require a comment refuse changes without one.

Resources

Resource What Import ID
featflags_project A project. project
featflags_environment An environment, with its allowed web origins and whether it requires comments. Exports its client_key and server_key. project/environment
featflags_sdk_key Another client or server key for an environment, for rotation, with an optional expiry. Exports the key when it’s created; destroying it revokes it. project/environment/key_id
featflags_flag A flag: its variants, client visibility, and whether it’s temporary or archived. Destroying it archives it, then deletes it. project/flag
featflags_flag_state A flag in one environment: on or off, prerequisites, individual targets, default and off variant. project/flag/environment
featflags_flag_rule One targeting rule, placed by priority. project/flag/environment/rule
featflags_segment A reusable audience. project/segment
featflags_release_template A release template. project/template
featflags_flag_trigger A trigger URL. Exports the url, sensitive, when it’s created. project/flag/environment/trigger_id
featflags_webhook A webhook or Slack notification. Exports its signing secret when it’s created. project/webhook_id

The data source featflags_environment reads an existing environment’s keys.

An example

resource "featflags_project" "acme" {
  key  = "acme"
  name = "Acme"
}

resource "featflags_environment" "production" {
  project         = featflags_project.acme.key
  key             = "production"
  allowed_origins = ["https://app.acme.com"]
  require_comment = true
}

resource "featflags_segment" "staff" {
  project = featflags_project.acme.key
  key     = "staff"
  match   = [{ conditions = [{ op = "ends_with", attr = "email", values = ["@acme.com"] }] }]
}

resource "featflags_flag" "new_checkout" {
  project        = featflags_project.acme.key
  key            = "new-checkout"
  description    = "Checkout rewrite"
  client_visible = true   # a boolean flag: variants on (true) and off (false)
}

resource "featflags_flag_state" "new_checkout_production" {
  project     = featflags_project.acme.key
  flag        = featflags_flag.new_checkout.key
  environment = featflags_environment.production.key
  enabled     = true

  default = {
    rollout = {
      split = [
        { variant = "on", percent = 5 },
        { variant = "off" },   # no percent: gets the rest
      ]
    }
  }
}

resource "featflags_flag_rule" "new_checkout_staff" {
  project     = featflags_project.acme.key
  flag        = featflags_flag.new_checkout.key
  environment = featflags_environment.production.key
  rule_id     = "staff"
  priority    = 10
  conditions  = [{ op = "in_segment", segments = [featflags_segment.staff.key] }]
  serve       = { variant = "on" }
}

In a rollout’s split, leave percent out of one entry and it gets whatever the others leave.

Working alongside the app

  • Rules are separate resources, so rules people add in the app live next to the ones Terraform manages, ordered by priority.
  • To let people flip a flag in the app without Terraform turning it back, ignore enabled in the flag state’s lifecycle { ignore_changes = [enabled] }.
  • Keep long key lists out of Terraform. Individual targets and in_secure values are plain text in Terraform’s state. Upload big lists with featflags lists upload; the provider leaves uploaded lists alone.
  • Approvals. In an environment that requires approval, a change Terraform makes is held for review instead of applied: the apply fails naming the held change and records nothing in state, so the next plan shows the change again until it’s approved in featflags.
  • Rate limits. When featflags asks it to slow down, the provider waits and retries, up to five attempts.