Skip to main content
The Terraform provider is in public preview and can change before reaching general availability.
The Braintrust Terraform provider manages your organization’s resources as code. Published on the Terraform Registry as braintrustdata/braintrust, it covers projects, datasets, functions, scores, tags, permission groups, project groups, roles, ACLs, service accounts and tokens, AI provider secrets, environment variables, automations (including retention), saved views, and MCP servers. The Braintrust Terraform Registry entry documents every resource and data source, including their arguments. The provider’s source code, examples, and releases are in the terraform-provider-braintrust GitHub repository.
The provider manages resources inside a Braintrust organization that already exists, and doesn’t provision cloud infrastructure. To deploy a self-hosted data plane in your own cloud account, use the data plane Terraform modules.

What to manage with Terraform

Terraform works best for stable configuration that a platform team governs centrally and shares across teams or environments:
  • Access and identity: Permission groups, roles, ACLs, service accounts, and service tokens.
  • Project scaffolding: Projects, score definitions, tags, and datasets (the containers, not their rows).
  • Organization policy: AI provider secrets, environment variables, and automations, including retention.
Keep content that changes often in your application code and its deployment pipeline instead:
  • Frequently changing prompts and functions: Push these with the SDK or bt functions push so they ship with your application. Terraform doesn’t build or upload code bundles, so functions with local dependencies need the push workflow.
  • Data and events: Dataset rows, logs, and experiment results.
The provider doesn’t create organizations, invite members, or create user API keys. Invite members in Braintrust, then reference them in Terraform by email.

Example: Set up a support team

This example walks through a realistic configuration, from an empty directory to a working terraform apply, that shows how the Braintrust Terraform provider’s resources fit together so you can adapt them to your own organization. The configuration sets up Braintrust for a support team that builds a customer support bot, with:
  • Production and staging projects, so changes tested in staging don’t affect production.
  • Two permission groups: engineers who build in both projects, and reviewers who view results without changing anything.
  • A shared prompt that triages support tickets, and a scorer that checks the prompt’s answers.
Each step adds blocks to the same main.tf file. The complete configuration is at the end of the example.

Prerequisites

Custom permission groups and project-level permissions are only available on the Enterprise plan.
The example also requires:
  • Terraform 1.0.3 or later.
  • The members you want in each group, already invited to your organization.
  • A Braintrust API key or service token for your organization.

1. Install and authenticate

Terraform needs the Braintrust provider and a credential before it can change anything in your organization. The terraform block names the provider to download from the Terraform Registry. Uncomment version and set it to the latest release so that future provider updates don’t change your configuration’s behavior unexpectedly. The provider block configures the provider. Left empty, it reads your credential from the BRAINTRUST_API_KEY environment variable. In an empty directory, create main.tf:
main.tf
Set your API key, then run terraform init to download the provider:
If your organization uses the EU data plane or a self-hosted data plane, also set the API URL. To run Terraform from a CI pipeline, use a service token instead of a personal API key. See Authenticate in CI.

2. Look up members

Permission groups identify members by user ID, not email. The braintrust_user data source looks up an existing member by email and returns the user ID that step 4 needs, without changing anything in Braintrust. The code lists the engineer and reviewer emails in two variables, then looks up each email. Replace the default emails with your members’ emails. Each email must belong to a member of your organization.
main.tf

3. Create projects

In this example, the support team needs two projects: one for the live support bot, and a staging project for testing changes before they reach production. Keeping them separate also lets the team grant access to each one independently. The braintrust_project resource creates both.
main.tf
If a project with the same name already exists in your organization, terraform apply fails instead of taking it over. Import the project first.

4. Create permission groups and grant access

Engineers build and change things, while reviewers only view results, so the two groups need different access to both projects. Braintrust grants access through three pieces, each with its own block in the provider:
  • Permission groups (braintrust_group) let you grant access to several members at once.
  • Roles (braintrust_role data source) are sets of permissions. Braintrust includes built-in roles such as Engineer and Viewer.
  • ACLs (braintrust_acl) grant one group one role on one object, such as a project.
The code gives the Support engineers group the Engineer role and the Support reviewers group the Viewer role, with one ACL for each group on each project:
main.tf
If your organization has a custom role named Engineer or Viewer, looking up the role by name fails. Look it up by id instead. If a group with the same name already exists, terraform apply fails instead of taking it over. Import the group first, or use a different name. Grant access to every project These ACLs cover only the two support projects. To grant a group a role on every project in your organization, including projects created later, set object_type to org_project and object_id to your organization ID. Any project’s org_id attribute holds that ID:

5. Add a prompt and a scorer

The support bot needs a prompt that triages tickets and a scorer that checks the prompt’s answers. Both change rarely and are shared across teams, which makes them a good fit for Terraform. The braintrust_function resource manages prompts and functions such as scorers, tools, and preprocessors. Two arguments describe the function, and both take JSON strings in the shape the Braintrust API accepts:
  • function_data_json sets the function’s type, such as prompt or code.
  • prompt_data_json sets a prompt’s messages and model. Only prompts use it.
The jsonencode() function turns Terraform values into these JSON strings, so you don’t have to write raw JSON. The code creates a prompt that classifies a ticket as billing, bug, or how-to. It also creates a scorer that returns 1 if the prompt’s answer is one of those categories, and 0 if it isn’t:
main.tf
Manage each function from one place, either Terraform or your push workflow, but not both. Keep prompts that your application team changes often in application code. To reference a function that Terraform doesn’t manage, such as one you push with bt functions push, use the braintrust_function data source. If a function with the same slug already exists in the project, terraform apply fails instead of overwriting it. Import the function first, or use a different slug.

6. Apply and verify

With the configuration complete, Terraform can create everything in Braintrust. terraform plan shows what Terraform will create, change, or delete, without changing anything. terraform apply shows the same plan, asks you to confirm, and then makes the changes.
After the apply completes, check that everything appears in Braintrust: From here, change the configuration and run terraform apply again to update Braintrust. Terraform records what it created in a local state file, which can hold secrets. Before you manage your organization’s real configuration, see Use the provider in production.

Complete configuration

The complete main.tf for this example:
main.tf

Use the provider in production

The example above runs from your machine with a personal API key, a local state file, and resources that don’t exist yet. Managing your organization’s real configuration also means planning for:

Authenticate in CI

The example authenticates with your personal API key. In production, run Terraform from a CI pipeline with a service token instead. A service token belongs to a service account rather than a person, so pipeline runs don’t depend on any one member’s access. Like any credential, it can change only what its account has permission to change. The provider accepts the token in either of two ways:
  • BRAINTRUST_API_KEY environment variable (recommended): Store the token in your CI system’s secret store and expose it to Terraform as BRAINTRUST_API_KEY. Your configuration doesn’t change.
  • api_key argument: Set api_key in the provider block from a variable, never from a key written in the file. Supply the variable’s value outside version control, for example with a TF_VAR_braintrust_api_key environment variable.
If you use the api_key argument, mark the variable sensitive to hide the value in Terraform’s output, and ephemeral to keep it out of state and plan files. ephemeral requires Terraform 1.10 or later.

Manage service tokens

The braintrust_service_token resource creates service tokens, but it requires a more privileged credential than other resources. User API keys can’t create service tokens, so the resource must use a provider block that authenticates with a service token whose account:
  • Has organization-owner permissions, or is an owner of a project group.
  • Holds all of the current permissions of the service account that the new token belongs to, including permissions outside any project group.
If the privileged token’s account owns a project group but isn’t an organization owner, the new token’s service account must have access granted on that project group’s projects. Access granted on an individual project in the group, or only on the project group’s definition, doesn’t count.
To learn how service account permissions work, see Use service accounts.
The service token your pipeline uses for other resources might not have these permissions. Instead of giving it more access, give Terraform a second, more privileged credential that only your braintrust_service_token resources use:
  1. Keep your existing provider "braintrust" block. It authenticates with your usual credential, and every resource uses it by default.
  2. Add a second provider "braintrust" block with an alias, which names the block, and set its api_key to the privileged service token.
  3. On each braintrust_service_token resource, set provider = braintrust.<alias> so that resource uses the second block.

Set the API URL

The provider sends requests to https://api.braintrust.dev by default. If your organization uses the EU data plane or a self-hosted data plane, set api_url in the provider block or the BRAINTRUST_API_URL environment variable:
You can find your organization’s API URL in Settings > Data plane.

Import existing resources

Importing links a resource that already exists in Braintrust, such as a project someone created in the UI, to a resource block in your configuration. Terraform then manages it as if it had created it: terraform apply updates it to match your configuration, and terraform destroy deletes it. Without an import, terraform apply usually fails when it tries to create a resource that already exists. Environment variables and AI secrets are the exception, as Make changes safely explains. You can import every resource except braintrust_service_account and braintrust_service_token. 1. Find the import ID For most resources, the import ID is the resource’s Braintrust ID. These resources use a composite ID instead, made of several IDs joined by slashes: 2. Import the resource Terraform provides two ways to import:
On Terraform 1.5 or later, add an import block with the resource’s ID and the resource block to link it to, then run terraform apply. The import shows up in terraform plan for review, like any other change.
imports.tf
To have Terraform write the resource block for you, add only the import block and run terraform plan -generate-config-out=generated.tf. Review the generated file before you apply it.

Secure the state file

Terraform records every resource it manages in a state file, including these secret values in plaintext:
  • braintrust_service_token keys
  • braintrust_ai_secret credentials
  • braintrust_env_var values
Marking a value as sensitive hides it in terraform plan and terraform apply output, but doesn’t keep it out of state. Store state in a remote backend that encrypts it at rest and limits who can read it. Use a backend that supports state locking, so that two runs can’t change state at the same time.
Anyone who can read the state file can read these secrets. Never commit state to version control.

Make changes safely

Some provider behaviors affect what happens when you apply changes. For example, creating a resource that already exists usually fails, and changing an ACL removes access while Terraform replaces it. Review the following behaviors before you make changes.
If a project, dataset, function, score, tag, group, project group, role, ACL, automation, view, or MCP server with the same name or slug already exists, terraform apply fails without changing it. Import it first, or use a different name.In provider v0.0.6 and earlier, terraform apply takes over an existing group, role, or ACL instead of failing, and overwrites an existing function.
If an environment variable or AI secret with the same name already exists, terraform apply overwrites its value instead of failing, even after an import.
If someone edits a Terraform-managed resource in the Braintrust UI, the next terraform apply usually restores the configured values. Terraform can’t tell when someone changes an environment variable value or AI secret credential outside Terraform, because the API never returns those values. A change made in the Braintrust UI stays in place until you change the value in your configuration and apply it.
Terraform replaces an ACL on any change, and by default it deletes the old ACL before creating the new one. If the create fails, the access stays removed until you fix the configuration and apply again. To create the new ACL first, add Terraform’s create_before_destroy lifecycle setting to each braintrust_acl resource.
A token’s secret is available only when it’s created. If you lose the state that holds a token, delete the token in Braintrust and create a new one.

Next steps