| name | terraform-provider |
| description | Develops Terraform providers in Go. Use when creating a new Terraform provider or updating an existing one, use when creating, updating, deleting resources or data sources. |
| model | sonnet |
Requirements
Install the following tools:
With mise:
mise use terraform@1 go@1
Get started
When starting a new Terraform provider, use terraform-provider-scaffolding-framework GitHub repository.
This repository is a template for a Terraform provider. It is intended as a starting point for creating Terraform providers, containing:
- A resource and a data source (internal/provider/),
- Examples (examples/) and generated documentation (docs/),
- Miscellaneous meta files.
To simplify the bootstrapping of a new provider, clone the scaffolding provider:
cd /tmp && git clone https://github.com/hashicorp/terraform-provider-scaffolding-framework.git
And then, retrieve base resources from the cloned repository to initialize the new provider.
Adding Dependencies
A Terraform provider uses Go modules.
Please see the Go documentation for the most up to date information about using Go modules.
To add a new dependency github.com/author/dependency to your Terraform provider, always pin to an explicit version to avoid regressions. Never use @latest. First list available versions, then pick the most recent one:
go list -m -versions github.com/author/dependency
go get github.com/author/dependency@vX.Y.Z
go mod tidy
Then commit the changes to go.mod and go.sum.
Best practices
Follow the Terraform plugin framework documentation.
File naming
Always use snake_case for Terraform file names. For example:
data_source.tf not data-source.tf
main_resource.tf not main-resource.tf
Example folder naming
Place examples under examples/<resource_name>/ without provider prefix. For example:
examples/models/ not examples/anthropic_models/
examples/messages/ not examples/anthropic_messages/
Provider documentation structure
Follow the HashiCorp registry docs structure. The docs/ folder is exclusively user-facing and published to the Terraform Registry:
docs/
├── index.md # Required — provider overview and argument reference
├── data-sources/
│ └── <name>.md # One file per data source, no provider prefix
├── resources/
│ └── <name>.md # One file per resource, no provider prefix
├── functions/
│ └── <name>.md
└── guides/
└── <guide>.md # Optional user-facing guides
Each docs/data-sources/<name>.md and docs/resources/<name>.md must include YAML frontmatter:
---
page_title: "Anthropic: anthropic_<name>"
subcategory: "<Category>"
description: |-
One-line description of the resource or data source.
---
subcategory must never be empty. It controls how the Terraform Registry groups resources and data sources in its sidebar. An empty subcategory causes the resource/data source to appear at the top level, ungrouped, which clutters the navigation. Always assign a meaningful category (e.g. "Agents", "Messages", "Models").
docs/ files are auto-generated by make generate — never edit them directly. To set the subcategory, create a template under templates/ for each resource and data source:
templates/
├── resources/
│ └── <name>.md.tmpl # sets subcategory + embeds example + schema
└── data-sources/
└── <name>.md.tmpl
Minimal template structure:
---
page_title: "anthropic_<name> Resource - anthropic"
subcategory: "<Category>"
description: |-
One-line description.
---
# anthropic_<name> (Resource)
One-line description.
## Example Usage
{{ codefile "hcl" "examples/resources/<name>/resource.tf" }}
{{ .SchemaMarkdown | trimspace }}
Without a template, make generate will auto-generate the doc with subcategory: "". Whenever you add a new resource or data source, always create its template file at the same time.
docs/index.md frontmatter:
---
page_title: "Provider: Anthropic"
description: |-
Use the Anthropic Terraform provider to interact with Anthropic APIs.
---
Technical/maintainer documentation (release process, GPG setup, CI secrets) must NOT go in docs/ — it would be published to the registry. Place it at the repo root (e.g., RELEASE.md, CONTRIBUTING.md) instead.
Native Terraform tests
Use native Terraform tests (terraform test, requires Terraform >= 1.6) to test example modules end-to-end. Place test files in tests/ at the project root, one file per example root module.
File naming and structure
tests/
├── provider.tftest.hcl # Tests examples/provider/
└── <data_source_name>.tftest.hcl # Tests examples/data-sources/<name>/
Test file anatomy
Each test file may contain (in order):
- Zero or one
test block — file-level execution settings
- Zero or more
provider blocks — shared provider configuration
- Zero or one
variables block — file-level variable defaults
- One or more
run blocks — each is an independent test case
Always add test { parallel = true } at the top of every test file to allow eligible run blocks to execute simultaneously:
test {
parallel = true
}
run "models_data_source_returns_results" {
module {
source = "../examples/data-sources/models"
}
assert {
condition = length(output.models) > 0
error_message = "Expected at least one model to be returned, but got none."
}
assert {
condition = output.models[0].id != ""
error_message = "Expected the first model's id to be non-empty."
}
}
Paths in module { source = "..." } are relative to the working directory where terraform test runs (the project root), not the test file's location. Always use ./examples/... style paths, not ../examples/....
Run block options
| Option | Default | Purpose |
|---|
command | apply | apply (integration test) or plan (unit test, no real infra) |
module | — | Alternate module to test instead of root module |
variables | — | Variable overrides for this run |
assert | — | Validation conditions (each needs condition + error_message) |
expect_failures | — | Validate that a custom condition (validation/check block) fails |
parallel | false | Mark this run block as eligible for parallel execution |
state_key | — | Share state across run blocks that reference different modules |
Use command = plan when you only need to validate logic without creating real infrastructure (no API calls made). Use command = apply (the default) for full integration tests.
Running tests locally
Tests use a locally-built provider via Terraform dev overrides (make terraform-test handles this automatically). The provider requires at least one API key — set those needed by the resources under test:
# Standard resources (most resources and data sources)
ANTHROPIC_API_KEY=<key> make terraform-test
# Admin resources (anthropic_workspace, etc.)
ANTHROPIC_ADMIN_API_KEY=<admin-key> make terraform-test
# Both (when testing a mix)
ANTHROPIC_API_KEY=<key> ANTHROPIC_ADMIN_API_KEY=<admin-key> make terraform-test
Alternatively, store keys in the .env file at the project root (gitignored) and source it before running:
set -a && source .env && set +a && make terraform-test
terraform test automatically discovers .tftest.hcl files in the current directory and tests/ subdirectory.
Best practices
- Integration vs. unit: Default to
command = apply; use command = plan only for logic-only validation
- Assertion specificity: Assert exact attribute values when deterministic; use
!= "" or length() > 0 for dynamic API data
- Sensitive credentials: Pass via environment variables (
ANTHROPIC_API_KEY), never hardcode in test files
- Run ordering: Order
run blocks so that dependencies are created before they are referenced; destruction runs in reverse order
- Parallelism: A
run block with parallel = false (or no parallel attribute) acts as a synchronization barrier — subsequent runs wait for it