Comprehensive documentation and best practices for building Terraform providers with terraform-plugin-framework (v1.17.0). Covers providers, resources, schemas, types, validators, testing, and common pitfalls.
Overall
score
97%
Get started building Terraform providers with terraform-plugin-framework.
Add terraform-plugin-framework to your Go module:
go get github.com/hashicorp/terraform-plugin-framework@v1.17.0# Plugin testing framework
go get github.com/hashicorp/terraform-plugin-testing
# Plugin docs generator
go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latestmkdir terraform-provider-example
cd terraform-provider-example
go mod init github.com/example/terraform-provider-exampleCreate provider.go:
package main
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
)
type exampleProvider struct {
version string
}
func New(version string) func() provider.Provider {
return func() provider.Provider {
return &exampleProvider{
version: version,
}
}
}
func (p *exampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "example"
resp.Version = p.version
}
func (p *exampleProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"endpoint": schema.StringAttribute{
Optional: true,
},
},
}
}
func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
// Configure provider (read config, create API client, etc.)
}
func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
// Register resources here
}
}
func (p *exampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
// Register data sources here
}
}Create main.go:
package main
import (
"context"
"flag"
"log"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
var (
version string = "dev"
)
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers")
flag.Parse()
opts := providerserver.ServeOpts{
Address: "registry.terraform.io/example/example",
Debug: debug,
}
err := providerserver.Serve(context.Background(), New(version), opts)
if err != nil {
log.Fatal(err.Error())
}
}# Build provider
go build -o terraform-provider-example
# Install locally for testing
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/example/example/0.1.0/linux_amd64
cp terraform-provider-example ~/.terraform.d/plugins/registry.terraform.io/example/example/0.1.0/linux_amd64/
# Create test configuration
cat > main.tf <<EOF
terraform {
required_providers {
example = {
source = "registry.terraform.io/example/example"
version = "0.1.0"
}
}
}
provider "example" {
endpoint = "https://api.example.com"
}
EOF
# Test provider
terraform init
terraform plan| Feature | Framework | SDK (Legacy) |
|---|---|---|
| Protocol | v6 (latest) | v5 |
| Type System | Rich, extensible | Basic |
| Nested Schemas | Full support | Limited |
| Custom Types | Yes | No |
| Plan Modifiers | Yes | Limited |
| Validators | Built-in + custom | Manual |
| Status | ✅ Recommended | 🟡 Maintenance mode |
Add a Resource:
// 1. Define resource type
type petResource struct {
client *api.Client
}
// 2. Implement resource.Resource interface
func (r *petResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_pet"
}
func (r *petResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
// Define schema
}
// 3. Implement CRUD methods
func (r *petResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Create logic
}
// 4. Register in provider
func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
func() resource.Resource { return &petResource{} },
}
}Add Validation:
"name": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.LengthAtMost(100),
},
},Add Plan Modifier:
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},func (r *petResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data PetModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return // ALWAYS return early if errors
}
// Continue with create logic...
}// Check before using
if !data.Name.IsNull() && !data.Name.IsUnknown() {
name := data.Name.ValueString()
// Use name
}type PetModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Age types.Int64 `tfsdk:"age"`
Active types.Bool `tfsdk:"active"`
}Ready to dive deeper? Continue to Provider Implementation to learn about provider configuration and setup.