Provider Schema Design
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
All Optional (Recommended)
// ✅ 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
}
6. Group Related Configuration
// ✅ 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