Provider Schema Design

6 minute read

Provider Schema Fundamentals

Provider schemas define configuration at the provider level, not the resource level. They control how your entire provider behaves and authenticates.

Provider vs Resource Schemas

Resource Schema

// Resource schema - for individual resources
func (r *EnvironmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "name": schema.StringAttribute{
                Required: true,
            },
        },
    }
}
resource "mycloud_environment" "prod" {
  name = "production"  # Resource-level config
}

Provider Schema

// Provider schema - for entire provider
func (p *MyCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "api_key": schema.StringAttribute{
                Optional:  true,
                Sensitive: true,
            },
        },
    }
}
provider "mycloud" {
  api_key = "secret-key"  # Provider-level config
}

Basic Provider Schema

Simple Example

package provider

import (
    "context"
    
    "github.com/hashicorp/terraform-plugin-framework/provider"
    "github.com/hashicorp/terraform-plugin-framework/provider/schema"
    "github.com/hashicorp/terraform-plugin-framework/types"
)

type MyCloudProvider struct {
    version string
}

type MyCloudProviderModel struct {
    APIKey   types.String `tfsdk:"api_key"`
    Endpoint types.String `tfsdk:"endpoint"`
    Region   types.String `tfsdk:"region"`
}

func (p *MyCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        MarkdownDescription: "MyCloud provider for managing infrastructure",
        
        Attributes: map[string]schema.Attribute{
            "api_key": schema.StringAttribute{
                Optional:            true,
                Sensitive:           true,
                MarkdownDescription: "API key for authentication",
            },
            
            "endpoint": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "API endpoint URL",
            },
            
            "region": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "Default region for resources",
            },
        },
    }
}

Usage

provider "mycloud" {
  api_key  = var.api_key
  endpoint = "https://api.mycloud.com"
  region   = "us-east-1"
}

Common Provider Attributes

Authentication

"api_key": schema.StringAttribute{
    Optional:            true,
    Sensitive:           true,
    MarkdownDescription: "API key for authentication. Can also be set via MYCLOUD_API_KEY environment variable.",
},

"api_secret": schema.StringAttribute{
    Optional:            true,
    Sensitive:           true,
    MarkdownDescription: "API secret for authentication. Can also be set via MYCLOUD_API_SECRET environment variable.",
},

"token": schema.StringAttribute{
    Optional:            true,
    Sensitive:           true,
    MarkdownDescription: "Authentication token. Can also be set via MYCLOUD_TOKEN environment variable.",
},

Endpoint Configuration

"endpoint": schema.StringAttribute{
    Optional:            true,
    MarkdownDescription: "API endpoint URL. Defaults to https://api.mycloud.com",
},

"base_url": schema.StringAttribute{
    Optional:            true,
    MarkdownDescription: "Base URL for API requests",
},

Regional Settings

"region": schema.StringAttribute{
    Optional:            true,
    MarkdownDescription: "Default region for resources. Can be overridden per-resource.",
},

Timeout Configuration

"timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Default timeout for API requests in seconds. Defaults to 30.",
},

"connect_timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Connection timeout in seconds. Defaults to 10.",
},

"read_timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Read timeout in seconds. Defaults to 30.",
},

Retry Configuration

"max_retries": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Maximum number of retry attempts. Defaults to 3.",
},

"retry_delay": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Delay between retries in seconds. Defaults to 1.",
},

Debug/Logging

"debug": schema.BoolAttribute{
    Optional:            true,
    MarkdownDescription: "Enable debug logging. Defaults to false.",
},

"log_level": schema.StringAttribute{
    Optional:            true,
    MarkdownDescription: "Log level: 'debug', 'info', 'warn', 'error'. Defaults to 'info'.",
    Validators: []validator.String{
        stringvalidator.OneOf("debug", "info", "warn", "error"),
    },
},

Optional vs Required

// ✅ Good: All optional with defaults or environment variables
resp.Schema = schema.Schema{
    Attributes: map[string]schema.Attribute{
        "api_key": schema.StringAttribute{
            Optional:  true,  // Can use env var
            Sensitive: true,
        },
        "endpoint": schema.StringAttribute{
            Optional: true,  // Has default
        },
        "region": schema.StringAttribute{
            Optional: true,  // Has default
        },
    },
}

Why all optional?

  • Users can set via environment variables
  • Allows sensible defaults
  • Better user experience
  • More flexible

With Required (Less Common)

// ❌ Less flexible: Required attributes
"api_key": schema.StringAttribute{
    Required:  true,  // Forces explicit configuration
    Sensitive: true,
},

Only require if:

  • No reasonable default exists
  • Cannot be set via environment variable
  • Absolutely essential for provider function

Environment Variable Integration

Documentation Pattern

"api_key": schema.StringAttribute{
    Optional:            true,
    Sensitive:           true,
    MarkdownDescription: "API key for authentication. Can also be set via `MYCLOUD_API_KEY` environment variable.",
},

Implementation

func (p *MyCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
    var config MyCloudProviderModel
    diags := req.Config.Get(ctx, &config)
    resp.Diagnostics.Append(diags...)
    
    // Check explicit config first, then environment variable
    apiKey := config.APIKey.ValueString()
    if apiKey == "" {
        apiKey = os.Getenv("MYCLOUD_API_KEY")
    }
    
    if apiKey == "" {
        resp.Diagnostics.AddError(
            "Missing API Key",
            "API key must be provided via api_key attribute or MYCLOUD_API_KEY environment variable",
        )
        return
    }
    
    // Use apiKey...
}

Usage Patterns

# Option 1: Explicit configuration
provider "mycloud" {
  api_key = "secret-key"
}

# Option 2: Environment variable
# export MYCLOUD_API_KEY="secret-key"
provider "mycloud" {
  # api_key read from environment
}

# Option 3: Terraform variable
variable "mycloud_api_key" {
  type      = string
  sensitive = true
}

provider "mycloud" {
  api_key = var.mycloud_api_key
}

Default Values

In Configure Method

func (p *MyCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
    var config MyCloudProviderModel
    req.Config.Get(ctx, &config)
    
    // Apply defaults
    endpoint := config.Endpoint.ValueString()
    if endpoint == "" {
        endpoint = "https://api.mycloud.com"
    }
    
    region := config.Region.ValueString()
    if region == "" {
        region = "us-east-1"
    }
    
    timeout := 30
    if !config.Timeout.IsNull() {
        timeoutFloat, _ := config.Timeout.ValueBigFloat().Int64()
        timeout = int(timeoutFloat)
    }
    
    // Create client with defaults applied
    client := api.NewClient(api.Config{
        Endpoint: endpoint,
        Region:   region,
        Timeout:  timeout,
    })
}

Nested Configuration

Single Nested Block

"advanced": schema.SingleNestedAttribute{
    Optional:            true,
    MarkdownDescription: "Advanced configuration options",
    Attributes: map[string]schema.Attribute{
        "connection_pool_size": schema.NumberAttribute{
            Optional:            true,
            MarkdownDescription: "HTTP connection pool size. Defaults to 100.",
        },
        "keep_alive": schema.BoolAttribute{
            Optional:            true,
            MarkdownDescription: "Enable HTTP keep-alive. Defaults to true.",
        },
        "compression": schema.BoolAttribute{
            Optional:            true,
            MarkdownDescription: "Enable response compression. Defaults to true.",
        },
    },
},
provider "mycloud" {
  api_key = var.api_key
  
  advanced {
    connection_pool_size = 200
    keep_alive          = true
    compression         = true
  }
}

Model

type MyCloudProviderModel struct {
    APIKey   types.String           `tfsdk:"api_key"`
    Endpoint types.String           `tfsdk:"endpoint"`
    Advanced *AdvancedConfigModel   `tfsdk:"advanced"`
}

type AdvancedConfigModel struct {
    ConnectionPoolSize types.Number `tfsdk:"connection_pool_size"`
    KeepAlive          types.Bool   `tfsdk:"keep_alive"`
    Compression        types.Bool   `tfsdk:"compression"`
}

Complete Provider Schema Example

func (p *MyCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        MarkdownDescription: "MyCloud provider for managing cloud infrastructure",
        
        Attributes: map[string]schema.Attribute{
            // Authentication
            "api_key": schema.StringAttribute{
                Optional:            true,
                Sensitive:           true,
                MarkdownDescription: "API key for authentication. Can be set via `MYCLOUD_API_KEY` environment variable.",
            },
            
            "api_secret": schema.StringAttribute{
                Optional:            true,
                Sensitive:           true,
                MarkdownDescription: "API secret for authentication. Can be set via `MYCLOUD_API_SECRET` environment variable.",
            },
            
            // Endpoint configuration
            "endpoint": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "API endpoint URL. Defaults to `https://api.mycloud.com`.",
            },
            
            "region": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "Default region for resources. Defaults to `us-east-1`.",
                Validators: []validator.String{
                    stringvalidator.OneOf("us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"),
                },
            },
            
            // Timeout configuration
            "timeout": schema.NumberAttribute{
                Optional:            true,
                MarkdownDescription: "Request timeout in seconds. Defaults to 30.",
                Validators: []validator.Number{
                    numbervalidator.Between(1, 300),
                },
            },
            
            "connect_timeout": schema.NumberAttribute{
                Optional:            true,
                MarkdownDescription: "Connection timeout in seconds. Defaults to 10.",
                Validators: []validator.Number{
                    numbervalidator.Between(1, 60),
                },
            },
            
            // Retry configuration
            "max_retries": schema.NumberAttribute{
                Optional:            true,
                MarkdownDescription: "Maximum number of retry attempts. Defaults to 3.",
                Validators: []validator.Number{
                    numbervalidator.Between(0, 10),
                },
            },
            
            // Debug
            "debug": schema.BoolAttribute{
                Optional:            true,
                MarkdownDescription: "Enable debug logging. Defaults to false.",
            },
        },
    }
}

Best Practices

1. Make Attributes Optional

// ✅ Good: Optional with environment variable fallback
"api_key": schema.StringAttribute{
    Optional:  true,
    Sensitive: true,
}

// ❌ Bad: Required (less flexible)
"api_key": schema.StringAttribute{
    Required:  true,
    Sensitive: true,
}

2. Document Environment Variables

// ✅ Good: Clear documentation
"api_key": schema.StringAttribute{
    Optional:            true,
    Sensitive:           true,
    MarkdownDescription: "API key. Can be set via `MYCLOUD_API_KEY` environment variable.",
}

3. Provide Sensible Defaults

// In Configure method
endpoint := config.Endpoint.ValueString()
if endpoint == "" {
    endpoint = "https://api.mycloud.com"  // ✅ Sensible default
}

4. Validate Configuration

// Add validators for enum values
"region": schema.StringAttribute{
    Optional: true,
    Validators: []validator.String{
        stringvalidator.OneOf("us-east-1", "us-west-2", "eu-west-1"),
    },
}

5. Mark Sensitive Data

// ✅ Always mark credentials as sensitive
"api_key": schema.StringAttribute{
    Optional:  true,
    Sensitive: true,  // Hidden from logs
}
// ✅ Good: Grouped advanced options
"advanced": schema.SingleNestedAttribute{
    Optional: true,
    Attributes: map[string]schema.Attribute{
        "connection_pool_size": schema.NumberAttribute{...},
        "keep_alive": schema.BoolAttribute{...},
    },
}

Common Patterns

Pattern 1: Multiple Authentication Methods

"api_key": schema.StringAttribute{
    Optional:  true,
    Sensitive: true,
},

"username": schema.StringAttribute{
    Optional: true,
},

"password": schema.StringAttribute{
    Optional:  true,
    Sensitive: true,
},

Users choose one method:

# Method 1: API key
provider "mycloud" {
  api_key = var.api_key
}

# Method 2: Username/password
provider "mycloud" {
  username = "admin"
  password = var.password
}

Pattern 2: Regional Defaults

"region": schema.StringAttribute{
    Optional:            true,
    MarkdownDescription: "Default region. Can be overridden per-resource.",
}
provider "mycloud" {
  region = "us-west-2"  # Default for all resources
}

resource "mycloud_environment" "prod" {
  name   = "production"
  region = "us-east-1"  # Override for this resource
}

Pattern 3: Timeout Hierarchy

"timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Default timeout for all operations.",
},

"create_timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Timeout for create operations. Overrides timeout.",
},

"update_timeout": schema.NumberAttribute{
    Optional:            true,
    MarkdownDescription: "Timeout for update operations. Overrides timeout.",
},

Key Takeaways

  • ✅ Provider schemas configure the entire provider
  • ✅ Make attributes optional when possible
  • ✅ Support environment variables for credentials
  • ✅ Provide sensible defaults
  • ✅ Mark sensitive attributes
  • ✅ Validate configuration
  • ✅ Document all attributes thoroughly
  • ✅ Group related configuration in nested blocks

What’s Next?

Now let’s learn about implementing various authentication methods.

Next: Authentication Methods →


Previous: ← Provider Configuration Overview

Last updated: June 30, 2026

Updated: