diff --git a/CHANGELOG.md b/CHANGELOG.md
index feaedb045..2e7aa571d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## [Unreleased]
 
 - Add `ignore_missing_component_templates` to `elasticstack_elasticsearch_index_template` ([#1206](https://github.com/elastic/terraform-provider-elasticstack/pull/1206))
+- Migrate `elasticstack_elasticsearch_enrich_policy` resource and data source to Terraform Plugin Framework ([#1220](https://github.com/elastic/terraform-provider-elasticstack/pull/1220))
 - Prevent provider panic when a script exists in state, but not in Elasticsearch ([#1218](https://github.com/elastic/terraform-provider-elasticstack/pull/1218))
 
 ## [0.11.17] - 2025-07-21
diff --git a/docs/data-sources/elasticsearch_enrich_policy.md b/docs/data-sources/elasticsearch_enrich_policy.md
index be4f00bb7..9c286ae92 100644
--- a/docs/data-sources/elasticsearch_enrich_policy.md
+++ b/docs/data-sources/elasticsearch_enrich_policy.md
@@ -74,11 +74,35 @@ output "query" {
 
 - `name` (String) The name of the policy.
 
+### Optional
+
+- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection))
+
 ### Read-Only
 
 - `enrich_fields` (Set of String) Fields to add to matching incoming documents. These fields must be present in the source indices.
 - `id` (String) Internal identifier of the resource
 - `indices` (Set of String) Array of one or more source indices used to create the enrich index.
-- `match_field` (String) Field in source indices used to match incoming documents.
+- `match_field` (String) Field from the source indices used to match incoming documents.
 - `policy_type` (String) The type of enrich policy, can be one of geo_match, match, range.
 - `query` (String) Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.
+
+
+### Nested Schema for `elasticsearch_connection`
+
+Optional:
+
+- `api_key` (String, Sensitive) API Key to use for authentication to Elasticsearch
+- `bearer_token` (String, Sensitive) Bearer Token to use for authentication to Elasticsearch
+- `ca_data` (String) PEM-encoded custom Certificate Authority certificate
+- `ca_file` (String) Path to a custom Certificate Authority certificate
+- `cert_data` (String) PEM encoded certificate for client auth
+- `cert_file` (String) Path to a file containing the PEM encoded certificate for client auth
+- `endpoints` (List of String, Sensitive) A list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number.
+- `es_client_authentication` (String, Sensitive) ES Client Authentication field to be used with the JWT token
+- `headers` (Map of String, Sensitive) A list of headers to be sent with each request to Elasticsearch.
+- `insecure` (Boolean) Disable TLS certificate validation
+- `key_data` (String, Sensitive) PEM encoded private key for client auth
+- `key_file` (String) Path to a file containing the PEM encoded private key for client auth
+- `password` (String, Sensitive) Password to use for API authentication to Elasticsearch.
+- `username` (String) Username to use for API authentication to Elasticsearch.
diff --git a/docs/resources/elasticsearch_enrich_policy.md b/docs/resources/elasticsearch_enrich_policy.md
index 0fb926ffa..1845fc54e 100644
--- a/docs/resources/elasticsearch_enrich_policy.md
+++ b/docs/resources/elasticsearch_enrich_policy.md
@@ -58,13 +58,13 @@ resource "elasticstack_elasticsearch_enrich_policy" "policy1" {
 
 ### Optional
 
-- `elasticsearch_connection` (Block List, Max: 1, Deprecated) Elasticsearch connection configuration block. This property will be removed in a future provider version. Configure the Elasticsearch connection via the provider configuration instead. (see [below for nested schema](#nestedblock--elasticsearch_connection))
+- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection))
 - `execute` (Boolean) Whether to call the execute API function in order to create the enrich index.
 - `query` (String) Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.
 
 ### Read-Only
 
-- `id` (String) The ID of this resource.
+- `id` (String) Internal identifier of the resource
 
 
 ### Nested Schema for `elasticsearch_connection`
diff --git a/internal/elasticsearch/enrich/acc_test.go b/internal/elasticsearch/enrich/acc_test.go
new file mode 100644
index 000000000..cfbaf346e
--- /dev/null
+++ b/internal/elasticsearch/enrich/acc_test.go
@@ -0,0 +1,200 @@
+package enrich_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+)
+
+func TestAccResourceEnrichPolicyFW(t *testing.T) {
+	name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+	resource.Test(t, resource.TestCase{
+		PreCheck:                 func() { acctest.PreCheck(t) },
+		CheckDestroy:             checkEnrichPolicyDestroyFW(name),
+		ProtoV6ProviderFactories: acctest.Providers,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccEnrichPolicyFW(name),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "match_field", `email`),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "indices.0", name),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.0", "first_name"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.1", "last_name"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "query", "{\"match_all\": {}}\n"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccDataSourceEnrichPolicyFW(t *testing.T) {
+	name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+	resource.Test(t, resource.TestCase{
+		PreCheck:                 func() { acctest.PreCheck(t) },
+		ProtoV6ProviderFactories: acctest.Providers,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccEnrichPolicyDataSourceFW(name),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "name", name),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "policy_type", "match"),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "match_field", "email"),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "indices.0", name),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.0", "first_name"),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.1", "last_name"),
+					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "query", "{\"match_all\":{}}"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccResourceEnrichPolicyFromSDK(t *testing.T) {
+	name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+	resource.Test(t, resource.TestCase{
+		PreCheck: func() { acctest.PreCheck(t) },
+		Steps: []resource.TestStep{
+			{
+				// Create the enrich policy with the last provider version where the enrich policy resource was built on the SDK
+				ExternalProviders: map[string]resource.ExternalProvider{
+					"elasticstack": {
+						Source:            "elastic/elasticstack",
+						VersionConstraint: "0.11.17",
+					},
+				},
+				Config: testAccEnrichPolicyFW(name),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"),
+				),
+			},
+			{
+				ProtoV6ProviderFactories: acctest.Providers,
+				Config:                   testAccEnrichPolicyFW(name),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"),
+					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"),
+				),
+			},
+		},
+	})
+}
+
+func testAccEnrichPolicyFW(name string) string {
+	return fmt.Sprintf(`
+provider "elasticstack" {
+  elasticsearch {}
+}
+
+resource "elasticstack_elasticsearch_index" "my_index" {
+  name = "%s"
+
+  mappings = jsonencode({
+    properties = {
+      email      = { type = "text" }
+      first_name = { type = "text" }
+      last_name  = { type = "text" }
+    }
+  })
+  deletion_protection = false
+}
+
+resource "elasticstack_elasticsearch_enrich_policy" "policy" {
+  name          = "%s"
+  policy_type   = "match"
+  indices       = [elasticstack_elasticsearch_index.my_index.name]
+  match_field   = "email"
+  enrich_fields = ["first_name", "last_name"]
+	query = <<-EOD
+	{"match_all": {}}
+	EOD
+}
+	`, name, name)
+}
+
+func testAccEnrichPolicyDataSourceFW(name string) string {
+	return fmt.Sprintf(`
+provider "elasticstack" {
+  elasticsearch {}
+}
+
+resource "elasticstack_elasticsearch_index" "my_index" {
+  name = "%s"
+
+  mappings = jsonencode({
+    properties = {
+      email      = { type = "text" }
+      first_name = { type = "text" }
+      last_name  = { type = "text" }
+    }
+  })
+  deletion_protection = false
+}
+
+resource "elasticstack_elasticsearch_enrich_policy" "policy" {
+  name          = "%s"
+  policy_type   = "match"
+  indices       = [elasticstack_elasticsearch_index.my_index.name]
+  match_field   = "email"
+  enrich_fields = ["first_name", "last_name"]
+	query = <<-EOD
+	{"match_all": {}}
+	EOD
+}
+
+data "elasticstack_elasticsearch_enrich_policy" "test" {
+	name = elasticstack_elasticsearch_enrich_policy.policy.name
+}
+	`, name, name)
+}
+
+func checkEnrichPolicyDestroyFW(name string) func(s *terraform.State) error {
+	return func(s *terraform.State) error {
+		client, err := clients.NewAcceptanceTestingClient()
+		if err != nil {
+			return err
+		}
+
+		for _, rs := range s.RootModule().Resources {
+			if rs.Type != "elasticstack_elasticsearch_enrich_policy" {
+				continue
+			}
+			compId, _ := clients.CompositeIdFromStr(rs.Primary.ID)
+			if compId.ResourceId != name {
+				return fmt.Errorf("Found unexpectedly enrich policy: %s", compId.ResourceId)
+			}
+			esClient, err := client.GetESClient()
+			if err != nil {
+				return err
+			}
+			req := esClient.EnrichGetPolicy.WithName(compId.ResourceId)
+			res, err := esClient.EnrichGetPolicy(req)
+			if err != nil {
+				return err
+			}
+			defer res.Body.Close()
+			if res.StatusCode == http.StatusFound {
+				var policiesResponse map[string]any
+				if err := json.NewDecoder(res.Body).Decode(&policiesResponse); err != nil {
+					return err
+				}
+				if len(policiesResponse["policies"].([]any)) != 0 {
+					return fmt.Errorf("Enrich policy (%s) still exists", compId.ResourceId)
+				}
+			}
+		}
+		return nil
+	}
+}
diff --git a/internal/elasticsearch/enrich/create.go b/internal/elasticsearch/enrich/create.go
new file mode 100644
index 000000000..1f1f2e368
--- /dev/null
+++ b/internal/elasticsearch/enrich/create.go
@@ -0,0 +1,94 @@
+package enrich
+
+import (
+	"context"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+	"github.com/elastic/terraform-provider-elasticstack/internal/models"
+	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-framework/tfsdk"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func (r *enrichPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+	diags := r.upsert(ctx, req.Plan, &resp.State)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+}
+
+func (r *enrichPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+	diags := r.upsert(ctx, req.Plan, &resp.State)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+}
+
+func (r *enrichPolicyResource) upsert(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics {
+	var data EnrichPolicyDataWithExecute
+	var diags diag.Diagnostics
+	diags.Append(plan.Get(ctx, &data)...)
+	if diags.HasError() {
+		return diags
+	}
+
+	policyName := data.Name.ValueString()
+	id, sdkDiags := r.client.ID(ctx, policyName)
+	diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+	if diags.HasError() {
+		return diags
+	}
+
+	client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client)
+	diags.Append(diags...)
+	if diags.HasError() {
+		return diags
+	}
+
+	// Convert framework types to model
+	indices := utils.SetTypeAs[string](ctx, data.Indices, path.Empty(), &diags)
+	if diags.HasError() {
+		return diags
+	}
+
+	enrichFields := utils.SetTypeAs[string](ctx, data.EnrichFields, path.Empty(), &diags)
+	if diags.HasError() {
+		return diags
+	}
+
+	policy := &models.EnrichPolicy{
+		Type:         data.PolicyType.ValueString(),
+		Name:         policyName,
+		Indices:      indices,
+		MatchField:   data.MatchField.ValueString(),
+		EnrichFields: enrichFields,
+	}
+
+	if !data.Query.IsNull() && !data.Query.IsUnknown() {
+		policy.Query = data.Query.ValueString()
+	}
+
+	if sdkDiags := elasticsearch.PutEnrichPolicy(ctx, client, policy); sdkDiags.HasError() {
+		diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+		return diags
+	}
+
+	data.Id = types.StringValue(id.String())
+
+	// Execute policy if requested
+	if !data.Execute.IsNull() && !data.Execute.IsUnknown() && data.Execute.ValueBool() {
+		if sdkDiags := elasticsearch.ExecuteEnrichPolicy(ctx, client, policyName); sdkDiags.HasError() {
+			diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+			return diags
+		}
+	}
+
+	diags.Append(state.Set(ctx, &data)...)
+	return diags
+}
diff --git a/internal/elasticsearch/enrich/data_source.go b/internal/elasticsearch/enrich/data_source.go
new file mode 100644
index 000000000..446754661
--- /dev/null
+++ b/internal/elasticsearch/enrich/data_source.go
@@ -0,0 +1,122 @@
+package enrich
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+	"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+
+	providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema"
+)
+
+func NewEnrichPolicyDataSource() datasource.DataSource {
+	return &enrichPolicyDataSource{}
+}
+
+type enrichPolicyDataSource struct {
+	client *clients.ApiClient
+}
+
+func (d *enrichPolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_elasticsearch_enrich_policy"
+}
+
+func (d *enrichPolicyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+	client, diags := clients.ConvertProviderData(req.ProviderData)
+	resp.Diagnostics.Append(diags...)
+	d.client = client
+}
+
+func (d *enrichPolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	resp.Schema = GetDataSourceSchema()
+}
+
+func GetDataSourceSchema() schema.Schema {
+	return schema.Schema{
+		MarkdownDescription: "Returns information about an enrich policy. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/get-enrich-policy-api.html",
+		Blocks: map[string]schema.Block{
+			"elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false),
+		},
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: "Internal identifier of the resource",
+				Computed:            true,
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: "The name of the policy.",
+				Required:            true,
+			},
+			"policy_type": schema.StringAttribute{
+				MarkdownDescription: "The type of enrich policy, can be one of geo_match, match, range.",
+				Computed:            true,
+			},
+			"indices": schema.SetAttribute{
+				MarkdownDescription: "Array of one or more source indices used to create the enrich index.",
+				ElementType:         types.StringType,
+				Computed:            true,
+			},
+			"match_field": schema.StringAttribute{
+				MarkdownDescription: "Field from the source indices used to match incoming documents.",
+				Computed:            true,
+			},
+			"enrich_fields": schema.SetAttribute{
+				MarkdownDescription: "Fields to add to matching incoming documents. These fields must be present in the source indices.",
+				ElementType:         types.StringType,
+				Computed:            true,
+			},
+			"query": schema.StringAttribute{
+				MarkdownDescription: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.",
+				CustomType:          jsontypes.NormalizedType{},
+				Computed:            true,
+			},
+		},
+	}
+}
+
+func (d *enrichPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var data EnrichPolicyData
+	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	policyName := data.Name.ValueString()
+	client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, d.client)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id, sdkDiags := client.ID(ctx, policyName)
+	resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	data.Id = types.StringValue(id.String())
+
+	// Use the same read logic as the resource
+	policy, sdkDiags := elasticsearch.GetEnrichPolicy(ctx, client, policyName)
+	resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	if policy == nil {
+		resp.Diagnostics.AddError("Policy not found", fmt.Sprintf("Enrich policy '%s' not found", policyName))
+		return
+	}
+
+	// Convert model to framework types using shared function
+	data.populateFromPolicy(ctx, policy, &resp.Diagnostics)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/elasticsearch/enrich/delete.go b/internal/elasticsearch/enrich/delete.go
new file mode 100644
index 000000000..aa249f6d0
--- /dev/null
+++ b/internal/elasticsearch/enrich/delete.go
@@ -0,0 +1,34 @@
+package enrich
+
+import (
+	"context"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+)
+
+func (r *enrichPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+	var data EnrichPolicyDataWithExecute
+	resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString())
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	policyName := compId.ResourceId
+
+	client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	sdkDiags := elasticsearch.DeleteEnrichPolicy(ctx, client, policyName)
+	resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+}
diff --git a/internal/elasticsearch/enrich/models.go b/internal/elasticsearch/enrich/models.go
new file mode 100644
index 000000000..ca76c4216
--- /dev/null
+++ b/internal/elasticsearch/enrich/models.go
@@ -0,0 +1,49 @@
+package enrich
+
+import (
+	"context"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/models"
+	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+	"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type EnrichPolicyData struct {
+	Id                      types.String         `tfsdk:"id"`
+	ElasticsearchConnection types.List           `tfsdk:"elasticsearch_connection"`
+	Name                    types.String         `tfsdk:"name"`
+	PolicyType              types.String         `tfsdk:"policy_type"`
+	Indices                 types.Set            `tfsdk:"indices"`
+	MatchField              types.String         `tfsdk:"match_field"`
+	EnrichFields            types.Set            `tfsdk:"enrich_fields"`
+	Query                   jsontypes.Normalized `tfsdk:"query"`
+}
+
+type EnrichPolicyDataWithExecute struct {
+	EnrichPolicyData
+	Execute types.Bool `tfsdk:"execute"`
+}
+
+// populateFromPolicy converts models.EnrichPolicy to EnrichPolicyData fields
+func (data *EnrichPolicyData) populateFromPolicy(ctx context.Context, policy *models.EnrichPolicy, diagnostics *diag.Diagnostics) {
+	data.Name = types.StringValue(policy.Name)
+	data.PolicyType = types.StringValue(policy.Type)
+	data.MatchField = types.StringValue(policy.MatchField)
+
+	if policy.Query != "" && policy.Query != "null" {
+		data.Query = jsontypes.NewNormalizedValue(policy.Query)
+	} else {
+		data.Query = jsontypes.NewNormalizedNull()
+	}
+
+	// Convert string slices to Set
+	data.Indices = utils.SetValueFrom(ctx, policy.Indices, types.StringType, path.Empty(), diagnostics)
+	if diagnostics.HasError() {
+		return
+	}
+
+	data.EnrichFields = utils.SetValueFrom(ctx, policy.EnrichFields, types.StringType, path.Empty(), diagnostics)
+}
diff --git a/internal/elasticsearch/enrich/policy.go b/internal/elasticsearch/enrich/policy.go
deleted file mode 100644
index 16179c441..000000000
--- a/internal/elasticsearch/enrich/policy.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package enrich
-
-import (
-	"context"
-	"fmt"
-
-	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
-	"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
-	"github.com/elastic/terraform-provider-elasticstack/internal/models"
-	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
-	"github.com/hashicorp/terraform-plugin-log/tflog"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
-)
-
-func ResourceEnrichPolicy() *schema.Resource {
-	policySchema := map[string]*schema.Schema{
-		"name": {
-			Description: "Name of the enrich policy to manage.",
-			Type:        schema.TypeString,
-			Required:    true,
-			ForceNew:    true,
-		},
-		"policy_type": {
-			Description:  "The type of enrich policy, can be one of geo_match, match, range.",
-			Type:         schema.TypeString,
-			Required:     true,
-			ForceNew:     true,
-			ValidateFunc: validation.StringInSlice([]string{"geo_match", "match", "range"}, false),
-		},
-		"indices": {
-			Description: "Array of one or more source indices used to create the enrich index.",
-			Type:        schema.TypeSet,
-			Required:    true,
-			ForceNew:    true,
-			Elem: &schema.Schema{
-				Type: schema.TypeString,
-			},
-		},
-		"match_field": {
-			Description: "Field in source indices used to match incoming documents.",
-			Type:        schema.TypeString,
-			Required:    true,
-			ForceNew:    true,
-			ValidateFunc: validation.All(
-				validation.StringLenBetween(1, 255),
-			),
-		},
-		"enrich_fields": {
-			Description: "Fields to add to matching incoming documents. These fields must be present in the source indices.",
-			Type:        schema.TypeSet,
-			Required:    true,
-			ForceNew:    true,
-			Elem: &schema.Schema{
-				Type: schema.TypeString,
-			},
-		},
-		"query": {
-			Description:      "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.",
-			Type:             schema.TypeString,
-			Optional:         true,
-			ForceNew:         true,
-			ValidateFunc:     validation.StringIsJSON,
-			DiffSuppressFunc: utils.DiffJsonSuppress,
-		},
-		"execute": {
-			Description: "Whether to call the execute API function in order to create the enrich index.",
-			Type:        schema.TypeBool,
-			Optional:    true,
-			ForceNew:    true,
-			Default:     true,
-		},
-	}
-
-	utils.AddConnectionSchema(policySchema)
-
-	return &schema.Resource{
-		Description: "Managing Elasticsearch enrich policies, see: https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html",
-
-		CreateContext: resourceEnrichPolicyPut,
-		UpdateContext: resourceEnrichPolicyPut,
-		ReadContext:   resourceEnrichPolicyRead,
-		DeleteContext: resourceEnrichPolicyDelete,
-
-		Importer: &schema.ResourceImporter{
-			StateContext: schema.ImportStatePassthroughContext,
-		},
-
-		Schema: policySchema,
-	}
-}
-
-func resourceEnrichPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
-	client, diags := clients.NewApiClientFromSDKResource(d, meta)
-	if diags.HasError() {
-		return diags
-	}
-
-	compName, diags := clients.CompositeIdFromStr(d.Id())
-	if diags.HasError() {
-		return diags
-	}
-	policy, diags := elasticsearch.GetEnrichPolicy(ctx, client, compName.ResourceId)
-	if policy == nil && diags == nil {
-		tflog.Warn(ctx, fmt.Sprintf(`Enrich policy "%s" not found, removing from state`, compName.ResourceId))
-		d.SetId("")
-		return diags
-	}
-	if diags.HasError() {
-		return diags
-	}
-
-	if err := d.Set("name", policy.Name); err != nil {
-		return diag.FromErr(err)
-	}
-	if err := d.Set("policy_type", policy.Type); err != nil {
-		return diag.FromErr(err)
-	}
-	if err := d.Set("indices", policy.Indices); err != nil {
-		return diag.FromErr(err)
-	}
-	if err := d.Set("match_field", policy.MatchField); err != nil {
-		return diag.FromErr(err)
-	}
-	if err := d.Set("enrich_fields", policy.EnrichFields); err != nil {
-		return diag.FromErr(err)
-	}
-	if err := d.Set("query", policy.Query); err != nil {
-		return diag.FromErr(err)
-	}
-	return diags
-}
-
-func resourceEnrichPolicyPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
-	client, diags := clients.NewApiClientFromSDKResource(d, meta)
-	if diags.HasError() {
-		return diags
-	}
-
-	name := d.Get("name").(string)
-	id, diags := client.ID(ctx, name)
-	if diags.HasError() {
-		return diags
-	}
-	policy := &models.EnrichPolicy{
-		Type:         d.Get("policy_type").(string),
-		Name:         name,
-		Indices:      utils.ExpandStringSet(d.Get("indices").(*schema.Set)),
-		MatchField:   d.Get("match_field").(string),
-		EnrichFields: utils.ExpandStringSet(d.Get("enrich_fields").(*schema.Set)),
-	}
-
-	if query, ok := d.GetOk("query"); ok {
-		policy.Query = query.(string)
-	}
-
-	if diags = elasticsearch.PutEnrichPolicy(ctx, client, policy); diags.HasError() {
-		return diags
-	}
-	d.SetId(id.String())
-	if d.Get("execute").(bool) {
-		diags := elasticsearch.ExecuteEnrichPolicy(ctx, client, name)
-		if diags.HasError() {
-			return diags
-		}
-	}
-	return resourceEnrichPolicyRead(ctx, d, meta)
-}
-
-func resourceEnrichPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
-	client, diags := clients.NewApiClientFromSDKResource(d, meta)
-	if diags.HasError() {
-		return diags
-	}
-
-	compName, diags := clients.CompositeIdFromStr(d.Id())
-	if diags.HasError() {
-		return diags
-	}
-	return elasticsearch.DeleteEnrichPolicy(ctx, client, compName.ResourceId)
-}
diff --git a/internal/elasticsearch/enrich/policy_data_source.go b/internal/elasticsearch/enrich/policy_data_source.go
deleted file mode 100644
index 975c50294..000000000
--- a/internal/elasticsearch/enrich/policy_data_source.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package enrich
-
-import (
-	"context"
-
-	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func DataSourceEnrichPolicy() *schema.Resource {
-	policySchema := map[string]*schema.Schema{
-		"id": {
-			Description: "Internal identifier of the resource",
-			Type:        schema.TypeString,
-			Computed:    true,
-		},
-		"name": {
-			Description: "The name of the policy.",
-			Type:        schema.TypeString,
-			Required:    true,
-		},
-		"policy_type": {
-			Description: "The type of enrich policy, can be one of geo_match, match, range.",
-			Type:        schema.TypeString,
-			Computed:    true,
-		},
-		"indices": {
-			Description: "Array of one or more source indices used to create the enrich index.",
-			Type:        schema.TypeSet,
-			Computed:    true,
-			Elem: &schema.Schema{
-				Type: schema.TypeString,
-			},
-		},
-		"match_field": {
-			Description: "Field in source indices used to match incoming documents.",
-			Type:        schema.TypeString,
-			Computed:    true,
-		},
-		"enrich_fields": {
-			Description: "Fields to add to matching incoming documents. These fields must be present in the source indices.",
-			Type:        schema.TypeSet,
-			Computed:    true,
-			Elem: &schema.Schema{
-				Type: schema.TypeString,
-			},
-		},
-		"query": {
-			Description: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.",
-			Type:        schema.TypeString,
-			Computed:    true,
-		},
-	}
-
-	return &schema.Resource{
-		Description: "Returns information about an enrich policy. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/get-enrich-policy-api.html",
-		ReadContext: dataSourceEnrichPolicyRead,
-		Schema:      policySchema,
-	}
-}
-
-func dataSourceEnrichPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
-	client, diags := clients.NewApiClientFromSDKResource(d, meta)
-	if diags.HasError() {
-		return diags
-	}
-
-	policyId := d.Get("name").(string)
-	id, diags := client.ID(ctx, policyId)
-	if diags.HasError() {
-		return diags
-	}
-	d.SetId(id.String())
-	return resourceEnrichPolicyRead(ctx, d, meta)
-}
diff --git a/internal/elasticsearch/enrich/policy_data_source_test.go b/internal/elasticsearch/enrich/policy_data_source_test.go
deleted file mode 100644
index be51d1094..000000000
--- a/internal/elasticsearch/enrich/policy_data_source_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package enrich_test
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
-	sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
-)
-
-func TestAccDataSourceEnrichPolicy(t *testing.T) {
-	name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
-	resource.Test(t, resource.TestCase{
-		PreCheck:                 func() { acctest.PreCheck(t) },
-		ProtoV6ProviderFactories: acctest.Providers,
-		Steps: []resource.TestStep{
-			{
-				Config: testAccEnrichPolicyDataSource(name),
-				Check: resource.ComposeTestCheckFunc(
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "name", name),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "policy_type", "match"),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "match_field", "email"),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "indices.0", name),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.0", "first_name"),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.1", "last_name"),
-					resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "query", "{\"match_all\":{}}"),
-				),
-			},
-		},
-	})
-}
-
-func testAccEnrichPolicyDataSource(name string) string {
-	return fmt.Sprintf(`
-provider "elasticstack" {
-  elasticsearch {}
-}
-
-resource "elasticstack_elasticsearch_index" "my_index" {
-  name = "%s"
-
-  mappings = jsonencode({
-    properties = {
-      email      = { type = "text" }
-      first_name = { type = "text" }
-      last_name  = { type = "text" }
-    }
-  })
-  deletion_protection = false
-}
-
-resource "elasticstack_elasticsearch_enrich_policy" "policy" {
-  name          = "%s"
-  policy_type   = "match"
-  indices       = [elasticstack_elasticsearch_index.my_index.name]
-  match_field   = "email"
-  enrich_fields = ["first_name", "last_name"]
-	query = <<-EOD
-	{"match_all": {}}
-	EOD
-}
-
-data "elasticstack_elasticsearch_enrich_policy" "test" {
-	name = elasticstack_elasticsearch_enrich_policy.policy.name
-}
-	`, name, name)
-}
diff --git a/internal/elasticsearch/enrich/policy_test.go b/internal/elasticsearch/enrich/policy_test.go
deleted file mode 100644
index a33a6e3bb..000000000
--- a/internal/elasticsearch/enrich/policy_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package enrich_test
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
-	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
-	sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
-	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
-)
-
-func TestAccResourceEnrichPolicy(t *testing.T) {
-	name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
-	resource.Test(t, resource.TestCase{
-		PreCheck:                 func() { acctest.PreCheck(t) },
-		CheckDestroy:             checkEnrichPolicyDestroy(name),
-		ProtoV6ProviderFactories: acctest.Providers,
-		Steps: []resource.TestStep{
-			{
-				Config: testAccEnrichPolicy(name),
-				Check: resource.ComposeTestCheckFunc(
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "match_field", `email`),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "indices.0", name),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.0", "first_name"),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.1", "last_name"),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "query", "{\"match_all\":{}}"),
-					resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"),
-				),
-			},
-		},
-	})
-}
-
-func testAccEnrichPolicy(name string) string {
-	return fmt.Sprintf(`
-provider "elasticstack" {
-  elasticsearch {}
-}
-
-resource "elasticstack_elasticsearch_index" "my_index" {
-  name = "%s"
-
-  mappings = jsonencode({
-    properties = {
-      email      = { type = "text" }
-      first_name = { type = "text" }
-      last_name  = { type = "text" }
-    }
-  })
-  deletion_protection = false
-}
-
-resource "elasticstack_elasticsearch_enrich_policy" "policy" {
-  name          = "%s"
-  policy_type   = "match"
-  indices       = [elasticstack_elasticsearch_index.my_index.name]
-  match_field   = "email"
-  enrich_fields = ["first_name", "last_name"]
-	query = <<-EOD
-	{"match_all": {}}
-	EOD
-}
-	`, name, name)
-}
-
-func checkEnrichPolicyDestroy(name string) func(s *terraform.State) error {
-	return func(s *terraform.State) error {
-		client, err := clients.NewAcceptanceTestingClient()
-		if err != nil {
-			return err
-		}
-
-		for _, rs := range s.RootModule().Resources {
-			if rs.Type != "elasticstack_elasticsearch_enrich_policy" {
-				continue
-			}
-			compId, _ := clients.CompositeIdFromStr(rs.Primary.ID)
-			if compId.ResourceId != name {
-				return fmt.Errorf("Found unexpectedly enrich policy: %s", compId.ResourceId)
-			}
-			esClient, err := client.GetESClient()
-			if err != nil {
-				return err
-			}
-			req := esClient.EnrichGetPolicy.WithName(compId.ResourceId)
-			res, err := esClient.EnrichGetPolicy(req)
-			if err != nil {
-				return err
-			}
-			defer res.Body.Close()
-			if res.StatusCode == http.StatusFound {
-				var policiesResponse map[string]any
-				if err := json.NewDecoder(res.Body).Decode(&policiesResponse); err != nil {
-					return err
-				}
-				if len(policiesResponse["policies"].([]any)) != 0 {
-					return fmt.Errorf("Enrich policy (%s) still exists", compId.ResourceId)
-				}
-			}
-		}
-		return nil
-	}
-}
diff --git a/internal/elasticsearch/enrich/read.go b/internal/elasticsearch/enrich/read.go
new file mode 100644
index 000000000..7e7f366e9
--- /dev/null
+++ b/internal/elasticsearch/enrich/read.go
@@ -0,0 +1,53 @@
+package enrich
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+	"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func (r *enrichPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+	var data EnrichPolicyDataWithExecute
+	resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString())
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	policyName := compId.ResourceId
+
+	client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	policy, sdkDiags := elasticsearch.GetEnrichPolicy(ctx, client, policyName)
+	resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	if policy == nil {
+		tflog.Warn(ctx, fmt.Sprintf(`Enrich policy "%s" not found, removing from state`, policyName))
+		resp.State.RemoveResource(ctx)
+		return
+	}
+
+	// Convert model to framework types using shared function
+	data.populateFromPolicy(ctx, policy, &resp.Diagnostics)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/elasticsearch/enrich/resource.go b/internal/elasticsearch/enrich/resource.go
new file mode 100644
index 000000000..28da90c60
--- /dev/null
+++ b/internal/elasticsearch/enrich/resource.go
@@ -0,0 +1,127 @@
+package enrich
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
+	"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
+	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+
+	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+	providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema"
+)
+
+func NewEnrichPolicyResource() resource.Resource {
+	return &enrichPolicyResource{}
+}
+
+type enrichPolicyResource struct {
+	client *clients.ApiClient
+}
+
+func (r *enrichPolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_elasticsearch_enrich_policy"
+}
+
+func (r *enrichPolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+	client, diags := clients.ConvertProviderData(req.ProviderData)
+	resp.Diagnostics.Append(diags...)
+	r.client = client
+}
+
+func (r *enrichPolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+	resp.Schema = GetResourceSchema()
+}
+
+func GetResourceSchema() schema.Schema {
+	return schema.Schema{
+		MarkdownDescription: "Managing Elasticsearch enrich policies. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html",
+		Blocks: map[string]schema.Block{
+			"elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false),
+		},
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: "Internal identifier of the resource",
+				Computed:            true,
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: "Name of the enrich policy to manage.",
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+				Validators: []validator.String{
+					stringvalidator.LengthBetween(1, 255),
+				},
+			},
+			"policy_type": schema.StringAttribute{
+				MarkdownDescription: "The type of enrich policy, can be one of geo_match, match, range.",
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+				Validators: []validator.String{
+					stringvalidator.OneOf("geo_match", "match", "range"),
+				},
+			},
+			"indices": schema.SetAttribute{
+				MarkdownDescription: "Array of one or more source indices used to create the enrich index.",
+				ElementType:         types.StringType,
+				Required:            true,
+				PlanModifiers: []planmodifier.Set{
+					setplanmodifier.RequiresReplace(),
+				},
+				Validators: []validator.Set{
+					setvalidator.SizeAtLeast(1),
+				},
+			},
+			"match_field": schema.StringAttribute{
+				MarkdownDescription: "Field in source indices used to match incoming documents.",
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+				Validators: []validator.String{
+					stringvalidator.LengthBetween(1, 255),
+				},
+			},
+			"enrich_fields": schema.SetAttribute{
+				MarkdownDescription: "Fields to add to matching incoming documents. These fields must be present in the source indices.",
+				ElementType:         types.StringType,
+				Required:            true,
+				PlanModifiers: []planmodifier.Set{
+					setplanmodifier.RequiresReplace(),
+				},
+				Validators: []validator.Set{
+					setvalidator.SizeAtLeast(1),
+				},
+			},
+			"query": schema.StringAttribute{
+				MarkdownDescription: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.",
+				Optional:            true,
+				CustomType:          jsontypes.NormalizedType{},
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"execute": schema.BoolAttribute{
+				MarkdownDescription: "Whether to call the execute API function in order to create the enrich index.",
+				Optional:            true,
+				Computed:            true,
+				Default:             booldefault.StaticBool(true),
+				PlanModifiers: []planmodifier.Bool{
+					boolplanmodifier.RequiresReplace(),
+				},
+			},
+		},
+	}
+}
diff --git a/internal/utils/tfsdk.go b/internal/utils/tfsdk.go
index 275d467ea..eb594da44 100644
--- a/internal/utils/tfsdk.go
+++ b/internal/utils/tfsdk.go
@@ -12,6 +12,11 @@ import (
 	"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
 )
 
+type Elementable interface {
+	attr.Value
+	ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics
+}
+
 type ListMeta struct {
 	Context context.Context
 	Index   int
@@ -46,6 +51,22 @@ func ValueStringPointer(value types.String) *string {
 	return value.ValueStringPointer()
 }
 
+// ================
+// ==== Generic ===
+// ================
+
+// MapTypeAs converts a types.Map into a tfsdk aware map[string]T.
+func elementsAs[T any](ctx context.Context, value Elementable, p path.Path, diags *diag.Diagnostics) T {
+	var result T
+	if !IsKnown(value) {
+		return result
+	}
+
+	d := value.ElementsAs(ctx, &result, false)
+	diags.Append(ConvertToAttrDiags(d, p)...)
+	return result
+}
+
 // ================
 // ===== Maps =====
 // ================
@@ -107,14 +128,7 @@ func MapTypeToMap[T1 any, T2 any](ctx context.Context, value types.Map, p path.P
 
 // MapTypeAs converts a types.Map into a tfsdk aware map[string]T.
 func MapTypeAs[T any](ctx context.Context, value types.Map, p path.Path, diags *diag.Diagnostics) map[string]T {
-	if !IsKnown(value) {
-		return nil
-	}
-
-	var items map[string]T
-	d := value.ElementsAs(ctx, &items, false)
-	diags.Append(ConvertToAttrDiags(d, p)...)
-	return items
+	return elementsAs[map[string]T](ctx, value, p, diags)
 }
 
 // MapValueFrom converts a tfsdk aware map[string]T to a types.Map.
@@ -145,10 +159,7 @@ func SliceToListType[T1 any, T2 any](ctx context.Context, value []T1, elemType a
 // SliceToListType_String converts a tfsdk naive []string into a types.List.
 // This is a shorthand SliceToListType helper for strings.
 func SliceToListType_String(ctx context.Context, value []string, p path.Path, diags *diag.Diagnostics) types.List {
-	return SliceToListType(ctx, value, types.StringType, p, diags,
-		func(item string, meta ListMeta) types.String {
-			return types.StringValue(item)
-		})
+	return ListValueFrom(ctx, value, types.StringType, p, diags)
 }
 
 // ListTypeToMap converts a types.List first into a tfsdk aware map[string]T1
@@ -184,21 +195,12 @@ func ListTypeToSlice[T1 any, T2 any](ctx context.Context, value types.List, p pa
 // ListTypeToSlice_String converts a types.List into a []string.
 // This is a shorthand ListTypeToSlice helper for strings.
 func ListTypeToSlice_String(ctx context.Context, value types.List, p path.Path, diags *diag.Diagnostics) []string {
-	return ListTypeToSlice(ctx, value, p, diags, func(item types.String, meta ListMeta) string {
-		return item.ValueString()
-	})
+	return ListTypeAs[string](ctx, value, p, diags)
 }
 
 // ListTypeAs converts a types.List into a tfsdk aware []T.
 func ListTypeAs[T any](ctx context.Context, value types.List, p path.Path, diags *diag.Diagnostics) []T {
-	if !IsKnown(value) {
-		return nil
-	}
-
-	var items []T
-	nd := value.ElementsAs(ctx, &items, false)
-	diags.Append(ConvertToAttrDiags(nd, p)...)
-	return items
+	return elementsAs[[]T](ctx, value, p, diags)
 }
 
 // ListValueFrom converts a tfsdk aware []T to a types.List.
@@ -208,6 +210,21 @@ func ListValueFrom[T any](ctx context.Context, value []T, elemType attr.Type, p
 	return list
 }
 
+// ===================
+// ===== Sets =====
+// ===================
+
+// SetTypeAs converts a types.Set into a tfsdk aware []T.
+func SetTypeAs[T any](ctx context.Context, value types.Set, p path.Path, diags *diag.Diagnostics) []T {
+	return elementsAs[[]T](ctx, value, p, diags)
+}
+
+func SetValueFrom[T any](ctx context.Context, value []T, elemType attr.Type, p path.Path, diags *diag.Diagnostics) types.Set {
+	list, d := types.SetValueFrom(ctx, elemType, value)
+	diags.Append(ConvertToAttrDiags(d, p)...)
+	return list
+}
+
 // ===================
 // ===== Objects =====
 // ===================
diff --git a/internal/utils/tfsdk_test.go b/internal/utils/tfsdk_test.go
index 349cc8f8a..33aa3a5f1 100644
--- a/internal/utils/tfsdk_test.go
+++ b/internal/utils/tfsdk_test.go
@@ -100,6 +100,15 @@ var (
 		types.StringValue("v3"),
 	})
 
+	awareSetUnk   = types.SetUnknown(awareType)
+	awareSetNil   = types.SetNull(awareType)
+	awareSetEmpty = types.SetValueMust(awareType, []attr.Value{})
+	awareSetFull  = types.SetValueMust(awareType, []attr.Value{
+		types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id1")}),
+		types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id2")}),
+		types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id3")}),
+	})
+
 	normUnk   = jsontypes.NewNormalizedUnknown()
 	normNil   = jsontypes.NewNormalizedNull()
 	normEmpty = jsontypes.NewNormalizedValue(`{}`)
@@ -699,3 +708,52 @@ func TestTransformMapToSlice(t *testing.T) {
 		})
 	}
 }
+
+// Sets
+
+func TestSetTypeAs(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name  string
+		input types.Set
+		want  []aware
+	}{
+		{name: "converts unknown", input: awareSetUnk, want: awareSliceNil},
+		{name: "converts nil", input: awareSetNil, want: awareSliceNil},
+		{name: "converts empty", input: awareSetEmpty, want: awareSliceEmpty},
+		{name: "converts struct", input: awareSetFull, want: awareSliceFull},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var diags diag.Diagnostics
+			got := utils.SetTypeAs[aware](context.Background(), tt.input, path.Empty(), &diags)
+			require.Equal(t, tt.want, got)
+			require.Empty(t, diags)
+		})
+	}
+}
+
+func TestSetValueFrom(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name  string
+		input []aware
+		want  types.Set
+	}{
+		{name: "converts nil", input: awareSliceNil, want: awareSetNil},
+		{name: "converts empty", input: awareSliceEmpty, want: awareSetEmpty},
+		{name: "converts struct", input: awareSliceFull, want: awareSetFull},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var diags diag.Diagnostics
+			got := utils.SetValueFrom(context.Background(), tt.input, awareType, path.Empty(), &diags)
+			require.Equal(t, tt.want, got)
+			require.Empty(t, diags)
+		})
+	}
+}
diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go
index 1da87c988..d7031c9ce 100644
--- a/provider/plugin_framework.go
+++ b/provider/plugin_framework.go
@@ -6,6 +6,7 @@ import (
 	"github.com/elastic/terraform-provider-elasticstack/internal/apm/agent_configuration"
 	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
 	"github.com/elastic/terraform-provider-elasticstack/internal/clients/config"
+	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/enrich"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/index"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/indices"
@@ -86,6 +87,7 @@ func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSour
 		spaces.NewDataSource,
 		enrollment_tokens.NewDataSource,
 		integration_ds.NewDataSource,
+		enrich.NewEnrichPolicyDataSource,
 	}
 }
 
@@ -106,5 +108,6 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource {
 		output.NewResource,
 		server_host.NewResource,
 		system_user.NewSystemUserResource,
+		enrich.NewEnrichPolicyResource,
 	}
 }
diff --git a/provider/provider.go b/provider/provider.go
index f5bc7fba7..2cd6441ca 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -3,7 +3,6 @@ package provider
 import (
 	"github.com/elastic/terraform-provider-elasticstack/internal/clients"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/cluster"
-	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/enrich"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/ingest"
 	"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/logstash"
@@ -78,7 +77,6 @@ func New(version string) *schema.Provider {
 			"elasticstack_elasticsearch_security_user":                      security.DataSourceUser(),
 			"elasticstack_elasticsearch_snapshot_repository":                cluster.DataSourceSnapshotRespository(),
 			"elasticstack_elasticsearch_info":                               cluster.DataSourceClusterInfo(),
-			"elasticstack_elasticsearch_enrich_policy":                      enrich.DataSourceEnrichPolicy(),
 
 			"elasticstack_kibana_action_connector": kibana.DataSourceConnector(),
 			"elasticstack_kibana_security_role":    kibana.DataSourceRole(),
@@ -97,7 +95,6 @@ func New(version string) *schema.Provider {
 			"elasticstack_elasticsearch_snapshot_lifecycle":    cluster.ResourceSlm(),
 			"elasticstack_elasticsearch_snapshot_repository":   cluster.ResourceSnapshotRepository(),
 			"elasticstack_elasticsearch_script":                cluster.ResourceScript(),
-			"elasticstack_elasticsearch_enrich_policy":         enrich.ResourceEnrichPolicy(),
 			"elasticstack_elasticsearch_transform":             transform.ResourceTransform(),
 			"elasticstack_elasticsearch_watch":                 watcher.ResourceWatch(),