---
page_title: 'Resources - CustomizeDiff and PlanModifiers: Migrating from SDKv2 to the Framework'
description: >-
  Migrate resource customizediff functions in SDKv2 to plan modifiers in the plugin Framework.
---

# Plan Modification

Your provider can modify the Terraform plan to match the expected end state. This can include replacing unknown values
with expected known values or marking a resource that must be replaced. Refer to
[Plan Modification](/terraform/plugin/framework/resources/plan-modification) in the Framework documentation for details.

This page explains how to migrate resource `CustomizeDiff` functions in SDKv2 to `PlanModifiers` in the plugin
Framework.

## SDKv2
In SDKv2, plan modification is implemented with the `CustomizeDiff` field on the `schema.Resource` struct. The following
code shows a basic implementation of plan modification with SDKv2.

```go
func resourceExample() *schema.Resource {
    return &schema.Resource{
        CustomizeDiff: CustomizeDiffFunc,
        /* ... */
```

## Framework

In the Framework, you implement plan modification either by implementing the  `ResourceWithModifyPlan` interface on your
resource type, or by implementing `PlanModifiers` on individual attributes. This page demonstrates how to implement the
plan modifiers on individual attributes. Refer to
[Attributes - Force New](/terraform/plugin/framework/migrating/attributes-blocks/force-new) in this guide for further information on how
to implement a plan modifier on an attribute.

The `ResourceWithModifyPlan` interface requires a `ModifyPlan` function.

The following code shows how you can implement the `ModifyPlan` function on your `resource.Resource` type.

```go
func (r *resourceExample) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
    /* ... */
}
```
## Migration Notes
Remember the following differences between SDKv2 and the Framework when completing the migration.

- In SDKv2, you implement plan modification with the `CustomizeDiff` field on the `schema.Resource` struct. In the
Framework, you can either implement plan modification for the entire resource by implementing the
`ResourceWithModifyPlan` interface, or on individual attributes by adding `PlanModifiers` to your resource attributes.
- Many existing CustomizeDiff implementations may be better suited to implementation as attribute plan modifiers in the
Framework.

## Example

### SDKv2

In SDKv2, the `CustomizeDiff` field on the `schema.Resource` struct refers to a function or set of functions that
implement plan modification.

The following example shows the use of `CustomizeDiff` to keep two attributes
synchronized (i.e., ensure that they contain the same value) with SDKv2.

```go
func resourcePassword() *schema.Resource {
    /* ... */
    customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_one", "attribute_two"))
    customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_two", "attribute_one"))

    return &schema.Resource{
        /* ... */
        CustomizeDiff: customdiff.All(
            customizeDiffFuncs...,
        ),
    }
}
```

The following example shows the implementation of the `planSyncIfChange` function.

```go
func planSyncIfChange(key, keyToSync string) func(context.Context, *schema.ResourceDiff, interface{}) error {
    return customdiff.IfValueChange(
        key,
        func(ctx context.Context, oldValue, newValue, meta interface{}) bool {
            return oldValue != newValue
        },
        func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error {
            return d.SetNew(keyToSync, d.Get(key))
        },
    )
}
```

### Framework

Many existing `CustomizeDiff` implementations would be better suited to migration to attribute plan modifiers in the
Framework. This code shows the implementation using attribute plan modifiers with the Framework.

```go
func exampleSchema() schema.Schema {
    return schema.Schema{
        /* ... */
        Attributes: map[string]schema.Attribute{
            /* ... */
            "attribute_one": schema.BoolAttribute{
                /* ... */
                PlanModifiers: []planmodifier.Bool{
                    planmodifiers.SyncAttributePlanModifier(),
                    /* ... */
                },
            },

            "attribute_two": schema.BoolAttribute{
                /* ... */
                PlanModifiers: []planmodifier.Bool{
                    planmodifiers.SyncAttributePlanModifier(),
                    /* ... */
                },
            },
```

The following shows an implementation of `SyncAttributePlanModifier` in the Framework.

```go
func SyncAttributePlanModifier() planmodifier.Bool {
    return &syncAttributePlanModifier{}
}

type syncAttributePlanModifier struct {
}

func (d *syncAttributePlanModifier) Description(ctx context.Context) string {
    return "Ensures that attribute_one and attribute_two attributes are kept synchronised."
}

func (d *syncAttributePlanModifier) MarkdownDescription(ctx context.Context) string {
    return d.Description(ctx)
}

func (d *syncAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
    var attributeOne types.Bool
    diags := req.Plan.GetAttribute(ctx, path.Root("attribute_one"), &attributeOne)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

    var attributeTwo types.Bool
    req.Plan.GetAttribute(ctx, path.Root("attribute_two"), &attributeTwo)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

	if !attributeOne.IsNull() && !attributeTwo.IsNull() && (attributeOne.ValueBool() != attributeTwo.ValueBool()) {
		resp.Diagnostics.AddError(
			"attribute_one and attribute_two are both configured with different values",
			"attribute_one is deprecated, use attribute_two instead",
		)
		return
	}

    // Default to true for both attribute_one and attribute_two when both are null.
    if attributeOne.IsNull() && attributeTwo.IsNull() {
        resp.PlanValue = types.BoolValue(true)
        return
    }

    // Default to using value for attribute_two if attribute_one is null
    if attributeOne.IsNull() && !attributeTwo.IsNull() {
        resp.PlanValue = numericConfig
        return
    }

    // Default to using value for attribute_one if attribute_two is null
    if !attributeOne.IsNull() && attributeTwo.IsNull() {
        resp.PlanValue = numberConfig
        return
    }
}
```
