diff --git a/provider/agent.go b/provider/agent.go
index d0ce4070..01fb5801 100644
--- a/provider/agent.go
+++ b/provider/agent.go
@@ -43,18 +43,6 @@ func agentResource() *schema.Resource {
 				}
 			}
 
-			rawPlan := resourceData.GetRawPlan()
-			items := rawPlan.GetAttr("metadata").AsValueSlice()
-			itemKeys := map[string]struct{}{}
-			for _, item := range items {
-				key := valueAsString(item.GetAttr("key"))
-				_, exists := itemKeys[key]
-				if exists {
-					return diag.FromErr(xerrors.Errorf("duplicate agent metadata key %q", key))
-				}
-				itemKeys[key] = struct{}{}
-			}
-
 			return updateInitScript(resourceData, i)
 		},
 		ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
@@ -272,6 +260,32 @@ func agentResource() *schema.Resource {
 				Optional:    true,
 			},
 		},
+		CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i any) error {
+			if !rd.HasChange("metadata") {
+				return nil
+			}
+
+			keys := map[string]bool{}
+			metadata, ok := rd.Get("metadata").([]any)
+			if !ok {
+				return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata"))
+			}
+			for _, t := range metadata {
+				obj, ok := t.(map[string]any)
+				if !ok {
+					return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t)
+				}
+				key, ok := obj["key"].(string)
+				if !ok {
+					return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"])
+				}
+				if keys[key] {
+					return xerrors.Errorf("duplicate agent metadata key %q", key)
+				}
+				keys[key] = true
+			}
+			return nil
+		},
 	}
 }
 
diff --git a/provider/agent_test.go b/provider/agent_test.go
index 0df2cf2d..91c708ca 100644
--- a/provider/agent_test.go
+++ b/provider/agent_test.go
@@ -254,6 +254,7 @@ func TestAgent_MetadataDuplicateKeys(t *testing.T) {
 				}
 				`,
 			ExpectError: regexp.MustCompile("duplicate agent metadata key"),
+			PlanOnly:    true,
 		}},
 	})
 }
@@ -281,7 +282,7 @@ func TestAgent_DisplayApps(t *testing.T) {
 							web_terminal = false
 							port_forwarding_helper = false
 							ssh_helper = false
-						} 
+						}
 					}
 					`,
 				Check: func(state *terraform.State) error {
@@ -331,7 +332,7 @@ func TestAgent_DisplayApps(t *testing.T) {
 						display_apps {
 							vscode_insiders = true
 							web_terminal = true
-						} 
+						}
 					}
 					`,
 				Check: func(state *terraform.State) error {
@@ -426,7 +427,7 @@ func TestAgent_DisplayApps(t *testing.T) {
 							web_terminal = false
 							port_forwarding_helper = false
 							ssh_helper = false
-						} 
+						}
 					}
 					`,
 				ExpectError: regexp.MustCompile(`An argument named "fake_app" is not expected here.`),
diff --git a/provider/metadata.go b/provider/metadata.go
index 48a3e89d..535c700c 100644
--- a/provider/metadata.go
+++ b/provider/metadata.go
@@ -7,6 +7,7 @@ import (
 	"github.com/google/uuid"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"golang.org/x/xerrors"
 )
 
 func metadataResource() *schema.Resource {
@@ -111,5 +112,31 @@ func metadataResource() *schema.Resource {
 				},
 			},
 		},
+		CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error {
+			if !rd.HasChange("item") {
+				return nil
+			}
+
+			keys := map[string]bool{}
+			metadata, ok := rd.Get("item").([]any)
+			if !ok {
+				return xerrors.Errorf("unexpected type %T for items, expected []any", rd.Get("metadata"))
+			}
+			for _, t := range metadata {
+				obj, ok := t.(map[string]any)
+				if !ok {
+					return xerrors.Errorf("unexpected type %T for item, expected map[string]any", t)
+				}
+				key, ok := obj["key"].(string)
+				if !ok {
+					return xerrors.Errorf("unexpected type %T for items 'key' attribute, expected string", obj["key"])
+				}
+				if keys[key] {
+					return xerrors.Errorf("duplicate resource metadata key %q", key)
+				}
+				keys[key] = true
+			}
+			return nil
+		},
 	}
 }
diff --git a/provider/metadata_test.go b/provider/metadata_test.go
index 0243f48d..14cdf5bc 100644
--- a/provider/metadata_test.go
+++ b/provider/metadata_test.go
@@ -123,7 +123,8 @@ func TestMetadataDuplicateKeys(t *testing.T) {
 					}
 				}
 				`,
-			ExpectError: regexp.MustCompile("duplicate metadata key"),
+			PlanOnly:    true,
+			ExpectError: regexp.MustCompile("duplicate resource metadata key"),
 		}},
 	})
 }
diff --git a/provider/provider.go b/provider/provider.go
index 1a37b706..1d78f2dd 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -97,14 +97,8 @@ func populateIsNull(resourceData *schema.ResourceData) (result interface{}, err
 	items := rawPlan.GetAttr("item").AsValueSlice()
 
 	var resultItems []interface{}
-	itemKeys := map[string]struct{}{}
 	for _, item := range items {
 		key := valueAsString(item.GetAttr("key"))
-		_, exists := itemKeys[key]
-		if exists {
-			return nil, xerrors.Errorf("duplicate metadata key %q", key)
-		}
-		itemKeys[key] = struct{}{}
 		resultItem := map[string]interface{}{
 			"key":       key,
 			"value":     valueAsString(item.GetAttr("value")),