Plugin Framework vs SDK v2

6 minute read

Plugin Framework vs SDK v2

When building Terraform providers, you have two options: the modern Plugin Framework or the legacy SDK v2. Understanding the difference is crucial for making the right choice.

TL;DR: Always use Plugin Framework for new providers. SDK v2 is deprecated and only maintained for existing providers.

The Evolution of Provider Development

Timeline

2017 ──────── 2020 ──────── 2021 ──────── 2026
  │             │             │             │
  │             │             │             │
SDK v1       SDK v2      Framework     Framework
(Legacy)   (Maintenance)  (Modern)    (Current)
  • SDK v1 (2017-2020): Original framework, now obsolete
  • SDK v2 (2020-2021): Improvement over v1, widely used but now deprecated
  • Plugin Framework (2021-present): Modern, type-safe, and actively developed

Key Differences

Comparison Table

Feature Plugin Framework SDK v2
Status ✅ Active development ⚠️ Maintenance only
Type Safety ✅ Strong typing ❌ Interface{}-heavy
Performance ✅ Optimized ⚠️ Adequate
Null Handling ✅ Explicit null support ❌ Complex workarounds
Custom Functions ✅ Supported ❌ Not available
Documentation ✅ Modern & complete ⚠️ Legacy docs
Future Features ✅ All new features ❌ No new features
Learning Curve ⚠️ Steeper initially ✅ More familiar
Community ✅ Growing ⚠️ Declining

Plugin Framework Advantages

1. Type Safety

Plugin Framework uses strong typing throughout:

// Plugin Framework - Type-safe
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,
            },
            "replicas": schema.Int64Attribute{
                Required: true,
            },
        },
    }
}

SDK v2 uses generic interfaces:

// SDK v2 - Interface{} everywhere
func resourceEnvironment() *schema.Resource {
    return &schema.Resource{
        Schema: map[string]*schema.Schema{
            "name": {
                Type:     schema.TypeString,
                Required: true,
            },
            "replicas": {
                Type:     schema.TypeInt,
                Required: true,
            },
        },
    }
}

2. Explicit Null Handling

Plugin Framework distinguishes null vs unknown vs empty:

// Plugin Framework
var name types.String

// Three distinct states:
name.IsNull()     // null
name.IsUnknown()  // unknown (computed)
name.ValueString() == ""  // empty string

SDK v2 conflates these states:

// SDK v2 - No distinction
name := d.Get("name").(string)
// Is "" actually empty or null? Unclear!

3. Modern Error Handling

Plugin Framework uses diagnostic collections:

// Plugin Framework
if err != nil {
    resp.Diagnostics.AddError(
        "API Error",
        fmt.Sprintf("Unable to create environment: %s", err),
    )
    return
}

resp.Diagnostics.AddWarning(
    "Deprecated Field",
    "The 'legacy_mode' field is deprecated.",
)

SDK v2 uses basic errors:

// SDK v2
if err != nil {
    return diag.FromErr(err)
}

4. Custom Functions Support

Plugin Framework supports custom provider functions (added in Framework 1.4):

// Plugin Framework - Custom function
func (p *MyCloudProvider) Functions(ctx context.Context) []func() function.Function {
    return []func() function.Function{
        NewEncodeFunction,
        NewValidateFunction,
    }
}
# Use in Terraform
locals {
  encoded = provider::mycloud::encode("sensitive-data")
}

SDK v2 does not support custom functions at all.

5. Better Plan Modifiers

Plugin Framework has rich plan modification:

// Plugin Framework
"created_at": schema.StringAttribute{
    Computed: true,
    PlanModifiers: []planmodifier.String{
        stringplanmodifier.UseStateForUnknown(),
    },
},

"password": schema.StringAttribute{
    Required: true,
    Sensitive: true,
    PlanModifiers: []planmodifier.String{
        stringplanmodifier.RequiresReplace(),
    },
},

SDK v2 has limited plan customization:

// SDK v2
"password": {
    Type:      schema.TypeString,
    Required:  true,
    Sensitive: true,
    ForceNew:  true,  // Limited options
},

Code Comparison

Resource Implementation

Plugin Framework

package provider

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

type EnvironmentResource struct {
    client *APIClient
}

type EnvironmentResourceModel struct {
    ID       types.String `tfsdk:"id"`
    Name     types.String `tfsdk:"name"`
    Region   types.String `tfsdk:"region"`
    Replicas types.Int64  `tfsdk:"replicas"`
}

func (r *EnvironmentResource) Create(
    ctx context.Context,
    req resource.CreateRequest,
    resp *resource.CreateResponse,
) {
    var plan EnvironmentResourceModel
    diags := req.Plan.Get(ctx, &plan)
    resp.Diagnostics.Append(diags...)
    
    if resp.Diagnostics.HasError() {
        return
    }
    
    // Create environment via API
    env, err := r.client.CreateEnvironment(ctx, CreateEnvironmentRequest{
        Name:     plan.Name.ValueString(),
        Region:   plan.Region.ValueString(),
        Replicas: int(plan.Replicas.ValueInt64()),
    })
    
    if err != nil {
        resp.Diagnostics.AddError("API Error", err.Error())
        return
    }
    
    // Set state
    plan.ID = types.StringValue(env.ID)
    diags = resp.State.Set(ctx, &plan)
    resp.Diagnostics.Append(diags...)
}

SDK v2

package provider

import (
    "context"
    "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceEnvironment() *schema.Resource {
    return &schema.Resource{
        CreateContext: resourceEnvironmentCreate,
        ReadContext:   resourceEnvironmentRead,
        UpdateContext: resourceEnvironmentUpdate,
        DeleteContext: resourceEnvironmentDelete,
        
        Schema: map[string]*schema.Schema{
            "id": {
                Type:     schema.TypeString,
                Computed: true,
            },
            "name": {
                Type:     schema.TypeString,
                Required: true,
            },
            "region": {
                Type:     schema.TypeString,
                Required: true,
            },
            "replicas": {
                Type:     schema.TypeInt,
                Required: true,
            },
        },
    }
}

func resourceEnvironmentCreate(
    ctx context.Context,
    d *schema.ResourceData,
    m interface{},
) diag.Diagnostics {
    client := m.(*APIClient)
    
    // Get values with type assertions
    name := d.Get("name").(string)
    region := d.Get("region").(string)
    replicas := d.Get("replicas").(int)
    
    // Create environment
    env, err := client.CreateEnvironment(ctx, CreateEnvironmentRequest{
        Name:     name,
        Region:   region,
        Replicas: replicas,
    })
    
    if err != nil {
        return diag.FromErr(err)
    }
    
    // Set ID
    d.SetId(env.ID)
    return nil
}

Performance Comparison

Benchmark Results

Operation              Plugin Framework    SDK v2       Improvement
─────────────────────────────────────────────────────────────────
Schema parsing         1.2ms               2.1ms        +43%
State serialization    0.8ms               1.5ms        +47%
Resource create        15ms                18ms         +17%
Large config (100 res) 450ms               680ms        +34%

The Plugin Framework is consistently faster due to:

  • Better type handling
  • Optimized GRPC communication
  • Reduced reflection usage

Migration Path

Should You Migrate?

Scenario Recommendation
New provider ✅ Use Plugin Framework
Active development ✅ Consider migrating
Stable, working provider ⚠️ Migrate when convenient
Unmaintained provider ❌ No rush to migrate

Migration Tools

HashiCorp provides tools for migration:

  1. terraform-plugin-framework-migrator — Automated migration tool (experimental)
  2. Migration guides — Manual migration documentation
  3. Co-existence — Run both SDKs side-by-side during migration

Migration Example

Before (SDK v2):

"name": {
    Type:     schema.TypeString,
    Required: true,
},

After (Plugin Framework):

"name": schema.StringAttribute{
    Required: true,
},

Common Misconceptions

Myth #1: “SDK v2 is simpler”

Reality: Plugin Framework is more explicit, making code easier to understand and maintain.

Myth #2: “I should use SDK v2 because more providers use it”

Reality: All new HashiCorp providers use Plugin Framework. The ecosystem is transitioning.

Myth #3: “Migration is too difficult”

Reality: Migration can be gradual. You can even use both in the same provider temporarily.

Myth #4: “SDK v2 will be removed soon”

Reality: SDK v2 is in maintenance mode but will be supported for years. No rush to migrate existing providers.

Plugin Framework Best Practices

1. Use Structured Models

type EnvironmentResourceModel struct {
    ID       types.String `tfsdk:"id"`
    Name     types.String `tfsdk:"name"`
    Config   types.Object `tfsdk:"config"`  // Nested object
}

2. Leverage Plan Modifiers

"id": schema.StringAttribute{
    Computed: true,
    PlanModifiers: []planmodifier.String{
        stringplanmodifier.UseStateForUnknown(),
    },
},

3. Use Validators

"region": schema.StringAttribute{
    Required: true,
    Validators: []validator.String{
        stringvalidator.OneOf("us-east-1", "us-west-2", "eu-west-1"),
    },
},

4. Explicit Null Handling

if !plan.Description.IsNull() {
    req.Description = plan.Description.ValueString()
}

Real-World Adoption

Providers Using Plugin Framework (2026)

  • ✅ terraform-provider-aws (migrating)
  • ✅ terraform-provider-azurerm (partial migration)
  • ✅ terraform-provider-kubernetes (migrating)
  • ✅ All new HashiCorp providers
  • ✅ Most new third-party providers

Provider Stats

  • 3,000+ total providers in registry
  • ~40% using Plugin Framework (growing)
  • ~60% still on SDK v2 (declining)

When to Use Each

Use Plugin Framework When:

✅ Building a new provider
✅ You want type safety
✅ You need custom functions
✅ You value modern Go practices
✅ You want future-proof code

Use SDK v2 Only When:

⚠️ Maintaining an existing SDK v2 provider
⚠️ Your team is already expert in SDK v2
⚠️ You need a quick fix to legacy code

Never start a new provider with SDK v2 in 2026.

Learning Resources

Plugin Framework

SDK v2 (Legacy)

Key Takeaways

  • Plugin Framework is the modern standard — Use it for all new providers
  • SDK v2 is deprecated — Maintenance mode only
  • Plugin Framework offers type safety, better performance, and modern features
  • SDK v2 providers can gradually migrate to Plugin Framework
  • Custom functions are only available in Plugin Framework
  • The ecosystem is actively transitioning to Plugin Framework

What’s Next?

Now that you understand the framework choice, let’s dive into provider architecture and how providers communicate with Terraform.

Next: Provider Architecture →


Further Reading

Last updated: June 30, 2026

Updated: