Repository: envoyproxy/gateway
Pull request #4301 (2024-09-22 06:02:06)

ci: dual-stack support for kind cluster
# feat: dual-stack support for kind cluster

**What type of PR is this?**
feat: Add dual-stack (IPv4 and IPv6) support for kind cluster

**What this PR does / why we need it**:
This PR implements dual-stack (IPv4 and IPv6) support for our kind cluster setup. It modifies the `create-cluster.sh` script to enable both IPv4 and IPv6 networking in the kind cluster and configures MetalLB to use address pools for both protocols. This enhancement allows for testing and development in environments that require both IP protocols, improving our cluster's versatility and compatibility with modern network configurations.

Key changes:
1. Enable dual-stack networking in the kind cluster configuration
2. Calculate both IPv4 and IPv6 address ranges from the Docker network
3. Update MetalLB configuration to support both IPv4 and IPv6 address pools

**Which issue(s) this PR fixes**:
Related #184

**Additional context**:
This change modifies the `tools/hack/create-cluster.sh` script to:
- Add `ipFamily: dual` to the kind cluster configuration
- Calculate IPv4 and IPv6 address ranges based on the Docker network IPAM
- Update the MetalLB IPAddressPool to include both IPv4 and IPv6 ranges

These modifications ensure that our development environment can support and test dual-stack networking scenarios, which is crucial for modern Kubernetes deployments and aligns with the goals outlined in issue #184.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4301?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 65.72%. Comparing base [(`475cd61`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/475cd61389c2f71e6fce8b9cc81c29ee5e18cc29?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`604efe2`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/604efe21870d1bf407243095cc673b7cc884f910?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #4301      +/-   ##
==========================================
+ Coverage   65.66%   65.72%   +0.06%     
==========================================
  Files         197      197              
  Lines       23565    23565              
==========================================
+ Hits        15474    15489      +15     
+ Misses       6982     6968      -14     
+ Partials     1109     1108       -1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/4301?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).


Patch:
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 0b4cb36ec..c30624a04 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -62,7 +62,7 @@ jobs:
         extended: true
 
     - name: Setup Node
-      uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b  # v4.1.0
+      uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6  # v4.1.0
       with:
         node-version: '18'
 
diff --git a/api/v1alpha1/httproutefilter_types.go b/api/v1alpha1/httproutefilter_types.go
index b8fe13f52..e0e4755f2 100644
--- a/api/v1alpha1/httproutefilter_types.go
+++ b/api/v1alpha1/httproutefilter_types.go
@@ -56,14 +56,17 @@ const (
 type ReplaceRegexMatch struct {
 	// Pattern matches a regular expression against the value of the HTTP Path.The regex string must
 	// adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
+	// +kubebuilder:validation:MinLength=1
 	Pattern string `json:"pattern"`
 	// Substitution is an expression that replaces the matched portion.The expression may include numbered
 	// capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.
 	Substitution string `json:"substitution"`
 }
 
+// +kubebuilder:validation:XValidation:rule="self.type == 'ReplaceRegexMatch' ? has(self.replaceRegexMatch) : !has(self.replaceRegexMatch)",message="If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch field needs to be set."
 type HTTPPathModifier struct {
-	// +kubebuilder:validation:Enum=RegexHTTPPathModifier
+	// +kubebuilder:validation:Enum=ReplaceRegexMatch
+	// +kubebuilder:validation:Required
 	Type HTTPPathModifierType `json:"type"`
 	// ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.
 	// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite
@@ -84,6 +87,7 @@ type HTTPPathModifier struct {
 	//       pattern: (?i)/xxx/
 	//       substitution: /yyy/
 	//     Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive).
+	// +optional
 	ReplaceRegexMatch *ReplaceRegexMatch `json:"replaceRegexMatch,omitempty"`
 }
 
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml
index 2bba4d20f..11447cc47 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml
@@ -82,6 +82,7 @@ spec:
                             description: |-
                               Pattern matches a regular expression against the value of the HTTP Path.The regex string must
                               adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
+                            minLength: 1
                             type: string
                           substitution:
                             description: |-
@@ -96,11 +97,16 @@ spec:
                         description: HTTPPathModifierType defines the type of path
                           redirect or rewrite.
                         enum:
-                        - RegexHTTPPathModifier
+                        - ReplaceRegexMatch
                         type: string
                     required:
                     - type
                     type: object
+                    x-kubernetes-validations:
+                    - message: If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch
+                        field needs to be set.
+                      rule: 'self.type == ''ReplaceRegexMatch'' ? has(self.replaceRegexMatch)
+                        : !has(self.replaceRegexMatch)'
                 type: object
             type: object
         required:
diff --git a/charts/gateway-helm/templates/_rbac.tpl b/charts/gateway-helm/templates/_rbac.tpl
index fb9304e7d..27e90061b 100644
--- a/charts/gateway-helm/templates/_rbac.tpl
+++ b/charts/gateway-helm/templates/_rbac.tpl
@@ -71,6 +71,7 @@ resources:
 - securitypolicies
 - envoyextensionpolicies
 - backends
+- httproutefilters
 verbs:
 - get
 - list
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index c023ad3e2..c4d2cbcf7 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/envoyproxy/gateway v1.0.2
 	github.com/envoyproxy/go-control-plane v0.13.1-0.20240917224354-20d038a70568
 	github.com/urfave/cli/v2 v2.27.2
-	google.golang.org/grpc v1.66.2
+	google.golang.org/grpc v1.67.0
 	google.golang.org/protobuf v1.34.2
 	k8s.io/apimachinery v0.31.1
 	sigs.k8s.io/controller-runtime v0.19.0
@@ -14,9 +14,9 @@ require (
 )
 
 require (
-	cel.dev/expr v0.15.0 // indirect
+	cel.dev/expr v0.16.0 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
-	github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
+	github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
 	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index 920c977fb..99b3fb50e 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -1,9 +1,9 @@
-cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
-cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
+cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y=
+cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
 github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
 github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg=
+github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
 github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -109,8 +109,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
-google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
+google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/go.mod b/go.mod
index 0fe4eb442..bbe504923 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/Masterminds/semver/v3 v3.3.0
 	github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
-	github.com/docker/cli v27.2.0+incompatible
+	github.com/docker/cli v27.3.1+incompatible
 	github.com/dominikbraun/graph v0.23.0
 	github.com/envoyproxy/go-control-plane v0.13.1-0.20240917224354-20d038a70568
 	github.com/envoyproxy/ratelimit v1.4.1-0.20230427142404-e2a87f41d3a7
@@ -26,8 +26,8 @@ require (
 	github.com/google/go-containerregistry v0.20.2
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/miekg/dns v1.1.62
-	github.com/ohler55/ojg v1.24.0
-	github.com/prometheus/client_golang v1.20.3
+	github.com/ohler55/ojg v1.24.1
+	github.com/prometheus/client_golang v1.20.4
 	github.com/prometheus/common v0.59.1
 	github.com/spf13/cobra v1.8.1
 	github.com/spf13/pflag v1.0.5
@@ -62,7 +62,7 @@ require (
 )
 
 require (
-	github.com/docker/docker v27.2.0+incompatible
+	github.com/docker/docker v27.3.1+incompatible
 	github.com/replicatedhq/troubleshoot v0.102.0
 	google.golang.org/grpc v1.66.2
 )
diff --git a/go.sum b/go.sum
index 50c9631fe..603355075 100644
--- a/go.sum
+++ b/go.sum
@@ -179,13 +179,13 @@ github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfG
 github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs=
 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/cli v27.2.0+incompatible h1:yHD1QEB1/0vr5eBNpu8tncu8gWxg8EydFPOSKHzXSMM=
-github.com/docker/cli v27.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
+github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
-github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
+github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
 github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -612,8 +612,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/ohler55/ojg v1.24.0 h1:y2AVez6fPTszK/jPhaAYMCAzAoSleConMqSDD5wJKJg=
-github.com/ohler55/ojg v1.24.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
+github.com/ohler55/ojg v1.24.1 h1:PaVLelrNgT5/0ppPaUtey54tOVp245z33fkhL2jljjY=
+github.com/ohler55/ojg v1.24.1/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -672,8 +672,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
-github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
+github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go
index de6be3528..41acc4c76 100644
--- a/internal/gatewayapi/filters.go
+++ b/internal/gatewayapi/filters.go
@@ -7,11 +7,13 @@ package gatewayapi
 
 import (
 	"fmt"
+	"regexp"
 	"strings"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/status"
 	"github.com/envoyproxy/gateway/internal/ir"
@@ -150,16 +152,20 @@ func (t *Translator) processURLRewriteFilter(
 	filterContext *HTTPFiltersContext,
 ) {
 	if filterContext.URLRewrite != nil {
-		routeStatus := GetRouteStatus(filterContext.Route)
-		status.SetRouteStatusCondition(routeStatus,
-			filterContext.ParentRef.routeParentStatusIdx,
-			filterContext.Route.GetGeneration(),
-			gwapiv1.RouteConditionAccepted,
-			metav1.ConditionFalse,
-			gwapiv1.RouteReasonUnsupportedValue,
-			"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
-		)
-		return
+		if filterContext.URLRewrite.Hostname != nil ||
+			filterContext.URLRewrite.Path.FullReplace != nil ||
+			filterContext.URLRewrite.Path.PrefixMatchReplace != nil {
+			routeStatus := GetRouteStatus(filterContext.Route)
+			status.SetRouteStatusCondition(routeStatus,
+				filterContext.ParentRef.routeParentStatusIdx,
+				filterContext.Route.GetGeneration(),
+				gwapiv1.RouteConditionAccepted,
+				metav1.ConditionFalse,
+				gwapiv1.RouteReasonUnsupportedValue,
+				"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
+			)
+			return
+		}
 	}
 
 	if rewrite == nil {
@@ -215,8 +221,10 @@ func (t *Translator) processURLRewriteFilter(
 				return
 			}
 			if rewrite.Path.ReplaceFullPath != nil {
-				newURLRewrite.Path = &ir.HTTPPathModifier{
-					FullReplace: rewrite.Path.ReplaceFullPath,
+				newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
+					HTTPPathModifier: ir.HTTPPathModifier{
+						FullReplace: rewrite.Path.ReplaceFullPath,
+					},
 				}
 			}
 		case gwapiv1.PrefixMatchHTTPPathModifier:
@@ -247,8 +255,10 @@ func (t *Translator) processURLRewriteFilter(
 				return
 			}
 			if rewrite.Path.ReplacePrefixMatch != nil {
-				newURLRewrite.Path = &ir.HTTPPathModifier{
-					PrefixMatchReplace: rewrite.Path.ReplacePrefixMatch,
+				newURLRewrite.Path = &ir.ExtendedHTTPPathModifier{
+					HTTPPathModifier: ir.HTTPPathModifier{
+						PrefixMatchReplace: rewrite.Path.ReplacePrefixMatch,
+					},
 				}
 			}
 		default:
@@ -738,6 +748,84 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec
 	}
 
 	filterNs := filterContext.Route.GetNamespace()
+
+	if string(extFilter.Kind) == egv1a1.KindHTTPRouteFilter {
+		for _, hrf := range resources.HTTPRouteFilters {
+			if hrf.Namespace == filterNs && hrf.Name == string(extFilter.Name) &&
+				hrf.Spec.URLRewrite.Path.Type == egv1a1.RegexHTTPPathModifier {
+
+				if hrf.Spec.URLRewrite.Path.ReplaceRegexMatch == nil ||
+					hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern == "" {
+					errMsg := "ReplaceRegexMatch Pattern must be set when rewrite path type is \"ReplaceRegexMatch\""
+					routeStatus := GetRouteStatus(filterContext.Route)
+					status.SetRouteStatusCondition(routeStatus,
+						filterContext.ParentRef.routeParentStatusIdx,
+						filterContext.Route.GetGeneration(),
+						gwapiv1.RouteConditionAccepted,
+						metav1.ConditionFalse,
+						gwapiv1.RouteReasonUnsupportedValue,
+						errMsg,
+					)
+					return
+				} else if _, err := regexp.Compile(hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern); err != nil {
+					// Avoid envoy NACKs due to invalid regex.
+					// Golang's regexp is almost identical to RE2: https://pkg.go.dev/regexp/syntax
+					errMsg := "ReplaceRegexMatch must be a valid RE2 regular expression"
+					routeStatus := GetRouteStatus(filterContext.Route)
+					status.SetRouteStatusCondition(routeStatus,
+						filterContext.ParentRef.routeParentStatusIdx,
+						filterContext.Route.GetGeneration(),
+						gwapiv1.RouteConditionAccepted,
+						metav1.ConditionFalse,
+						gwapiv1.RouteReasonUnsupportedValue,
+						errMsg,
+					)
+					return
+				}
+
+				rmr := &ir.RegexMatchReplace{
+					Pattern:      hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Pattern,
+					Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution,
+				}
+
+				if filterContext.HTTPFilterIR.URLRewrite != nil {
+					// If path IR is already set - check for a conflict
+					if filterContext.HTTPFilterIR.URLRewrite.Path != nil {
+						path := filterContext.HTTPFilterIR.URLRewrite.Path
+						if path.RegexMatchReplace != nil || path.PrefixMatchReplace != nil || path.FullReplace != nil {
+							routeStatus := GetRouteStatus(filterContext.Route)
+							status.SetRouteStatusCondition(routeStatus,
+								filterContext.ParentRef.routeParentStatusIdx,
+								filterContext.Route.GetGeneration(),
+								gwapiv1.RouteConditionAccepted,
+								metav1.ConditionFalse,
+								gwapiv1.RouteReasonUnsupportedValue,
+								"Cannot configure multiple urlRewrite filters for a single HTTPRouteRule",
+							)
+							return
+						}
+					} else { // no path
+						filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{
+							RegexMatchReplace: rmr,
+						}
+						return
+					}
+				} else { // no url rewrite
+					filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{
+						Path: &ir.ExtendedHTTPPathModifier{
+							RegexMatchReplace: rmr,
+						},
+					}
+					return
+				}
+			}
+		}
+		errMsg := fmt.Sprintf("Unable to translate HTTPRouteFilter: %s/%s", filterNs,
+			extFilter.Name)
+		t.processUnresolvedHTTPFilter(errMsg, filterContext)
+		return
+	}
+
 	// This list of resources will be empty unless an extension is loaded (and introduces resources)
 	for _, res := range resources.ExtensionRefFilters {
 		if res.GetKind() == string(extFilter.Kind) && res.GetName() == string(extFilter.Name) && res.GetNamespace() == filterNs {
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index 885077f5c..9c5626d75 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -179,6 +179,9 @@ func ValidateHTTPRouteFilter(filter *gwapiv1.HTTPRouteFilter, extGKs ...schema.G
 		switch {
 		case filter.ExtensionRef == nil:
 			return errors.New("extensionRef field must be specified for an extended filter")
+		case string(filter.ExtensionRef.Group) == egv1a1.GroupVersion.Group &&
+			string(filter.ExtensionRef.Kind) == egv1a1.KindHTTPRouteFilter:
+			return nil
 		default:
 			for _, gk := range extGKs {
 				if filter.ExtensionRef.Group == gwapiv1.Group(gk.Group) &&
diff --git a/internal/gatewayapi/resource/load.go b/internal/gatewayapi/resource/load.go
index cacc3ebe6..317ad9341 100644
--- a/internal/gatewayapi/resource/load.go
+++ b/internal/gatewayapi/resource/load.go
@@ -265,6 +265,20 @@ func kubernetesYAMLToResources(str string, addMissingResources bool) (*Resources
 				Spec: typedSpec.(egv1a1.SecurityPolicySpec),
 			}
 			resources.SecurityPolicies = append(resources.SecurityPolicies, securityPolicy)
+		case KindHTTPRouteFilter:
+			typedSpec := spec.Interface()
+			httpRouteFilter := &egv1a1.HTTPRouteFilter{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       KindHTTPRouteFilter,
+					APIVersion: egv1a1.GroupVersion.String(),
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: namespace,
+					Name:      name,
+				},
+				Spec: typedSpec.(egv1a1.HTTPRouteFilterSpec),
+			}
+			resources.HTTPRouteFilters = append(resources.HTTPRouteFilters, httpRouteFilter)
 		}
 	}
 
diff --git a/internal/gatewayapi/resource/resource.go b/internal/gatewayapi/resource/resource.go
index a5d9e6fff..97468511f 100644
--- a/internal/gatewayapi/resource/resource.go
+++ b/internal/gatewayapi/resource/resource.go
@@ -63,6 +63,7 @@ type Resources struct {
 	EnvoyExtensionPolicies  []*egv1a1.EnvoyExtensionPolicy `json:"envoyExtensionPolicies,omitempty" yaml:"envoyExtensionPolicies,omitempty"`
 	ExtensionServerPolicies []unstructured.Unstructured    `json:"extensionServerPolicies,omitempty" yaml:"extensionServerPolicies,omitempty"`
 	Backends                []*egv1a1.Backend              `json:"backends,omitempty" yaml:"backends,omitempty"`
+	HTTPRouteFilters        []*egv1a1.HTTPRouteFilter      `json:"httpFilters,omitempty" yaml:"httpFilters,omitempty"`
 }
 
 func NewResources() *Resources {
@@ -86,6 +87,7 @@ func NewResources() *Resources {
 		EnvoyExtensionPolicies:  []*egv1a1.EnvoyExtensionPolicy{},
 		ExtensionServerPolicies: []unstructured.Unstructured{},
 		Backends:                []*egv1a1.Backend{},
+		HTTPRouteFilters:        []*egv1a1.HTTPRouteFilter{},
 	}
 }
 
diff --git a/internal/gatewayapi/resource/supported_kind.go b/internal/gatewayapi/resource/supported_kind.go
index 5c2c21954..e9d76e66c 100644
--- a/internal/gatewayapi/resource/supported_kind.go
+++ b/internal/gatewayapi/resource/supported_kind.go
@@ -26,4 +26,5 @@ const (
 	KindService              = "Service"
 	KindServiceImport        = "ServiceImport"
 	KindSecret               = "Secret"
+	KindHTTPRouteFilter      = "HTTPRouteFilter"
 )
diff --git a/internal/gatewayapi/resource/zz_generated.deepcopy.go b/internal/gatewayapi/resource/zz_generated.deepcopy.go
index 61cf9dfb4..06925b146 100644
--- a/internal/gatewayapi/resource/zz_generated.deepcopy.go
+++ b/internal/gatewayapi/resource/zz_generated.deepcopy.go
@@ -279,6 +279,17 @@ func (in *Resources) DeepCopyInto(out *Resources) {
 			}
 		}
 	}
+	if in.HTTPRouteFilters != nil {
+		in, out := &in.HTTPRouteFilters, &out.HTTPRouteFilters
+		*out = make([]*v1alpha1.HTTPRouteFilter, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(v1alpha1.HTTPRouteFilter)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources.
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index a40cd8547..86a2d3956 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -51,8 +51,9 @@ var (
 	ErrDirectResponseStatusInvalid             = errors.New("only HTTP status codes 100 - 599 are supported for DirectResponse")
 	ErrRedirectUnsupportedStatus               = errors.New("only HTTP status codes 301 and 302 are supported for redirect filters")
 	ErrRedirectUnsupportedScheme               = errors.New("only http and https are supported for the scheme in redirect filters")
-	ErrHTTPPathModifierDoubleReplace           = errors.New("redirect filter cannot have a path modifier that supplies both fullPathReplace and prefixMatchReplace")
-	ErrHTTPPathModifierNoReplace               = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace or prefixMatchReplace")
+	ErrHTTPPathModifierDoubleReplace           = errors.New("redirect filter cannot have a path modifier that supplies more than one of fullPathReplace, prefixMatchReplace and regexMatchReplace")
+	ErrHTTPPathModifierNoReplace               = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
+	ErrHTTPPathRegexModifierNoSetting          = errors.New("redirect filter cannot have a path modifier that does not supply either fullPathReplace, prefixMatchReplace or regexMatchReplace")
 	ErrAddHeaderEmptyName                      = errors.New("header modifier filter cannot configure a header without a name to be added")
 	ErrAddHeaderDuplicate                      = errors.New("header modifier filter attempts to add the same header more than once (case insensitive)")
 	ErrRemoveHeaderDuplicate                   = errors.New("header modifier filter attempts to remove the same header more than once (case insensitive)")
@@ -729,6 +730,18 @@ type UnstructuredRef struct {
 	Object *unstructured.Unstructured `json:"object,omitempty" yaml:"object,omitempty"`
 }
 
+// RegexMatchReplace defines the schema for modifying HTTP request path using regex.
+//
+// +k8s:deepcopy-gen=true
+type RegexMatchReplace struct {
+	// Pattern matches a regular expression against the value of the HTTP Path.The regex string must
+	// adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax.
+	Pattern string `json:"pattern" yaml:"pattern"`
+	// Substitution is an expression that replaces the matched portion.The expression may include numbered
+	// capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax.
+	Substitution string `json:"substitution" yaml:"substitution"`
+}
+
 // CORS holds the Cross-Origin Resource Sharing (CORS) policy for the route.
 //
 // +k8s:deepcopy-gen=true
@@ -1293,7 +1306,7 @@ func (r DirectResponse) Validate() error {
 // +k8s:deepcopy-gen=true
 type URLRewrite struct {
 	// Path contains config for rewriting the path of the request.
-	Path *HTTPPathModifier `json:"path,omitempty" yaml:"path,omitempty"`
+	Path *ExtendedHTTPPathModifier `json:"path,omitempty" yaml:"path,omitempty"`
 	// Hostname configures the replacement of the request's hostname.
 	Hostname *string `json:"hostname,omitempty" yaml:"hostname,omitempty"`
 }
@@ -1375,6 +1388,42 @@ func (r HTTPPathModifier) Validate() error {
 	return errs
 }
 
+// ExtendedHTTPPathModifier holds instructions for how to modify the path of a request on a redirect response
+// with both core gateway-api and extended envoy gateway capabilities
+// +k8s:deepcopy-gen=true
+type ExtendedHTTPPathModifier struct {
+	HTTPPathModifier `json:",inline" yaml:",inline"`
+	// RegexMatchReplace provides a regex to match an a replacement to perform on the path.
+	RegexMatchReplace *RegexMatchReplace `json:"regexMatchReplace,omitempty" yaml:"regexMatchReplace,omitempty"`
+}
+
+// Validate the fields within the HTTPPathModifier structure
+func (r ExtendedHTTPPathModifier) Validate() error {
+	var errs error
+
+	rewrites := []bool{r.RegexMatchReplace != nil, r.PrefixMatchReplace != nil, r.FullReplace != nil}
+	rwc := 0
+	for _, rw := range rewrites {
+		if rw {
+			rwc++
+		}
+	}
+
+	if rwc > 1 {
+		errs = errors.Join(errs, ErrHTTPPathModifierDoubleReplace)
+	}
+
+	if r.FullReplace == nil && r.PrefixMatchReplace == nil && r.RegexMatchReplace == nil {
+		errs = errors.Join(errs, ErrHTTPPathModifierNoReplace)
+	}
+
+	if r.RegexMatchReplace != nil && (r.RegexMatchReplace.Pattern == "" || r.RegexMatchReplace.Substitution == "") {
+		errs = errors.Join(errs, ErrHTTPPathModifierNoReplace)
+	}
+
+	return errs
+}
+
 // StringMatch holds the various match conditions.
 // Only one of Exact, Prefix, SafeRegex or Distinct can be set.
 // +k8s:deepcopy-gen=true
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 1639262be..0b964d5fc 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -909,6 +909,27 @@ func (in *ExtProc) DeepCopy() *ExtProc {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExtendedHTTPPathModifier) DeepCopyInto(out *ExtendedHTTPPathModifier) {
+	*out = *in
+	in.HTTPPathModifier.DeepCopyInto(&out.HTTPPathModifier)
+	if in.RegexMatchReplace != nil {
+		in, out := &in.RegexMatchReplace, &out.RegexMatchReplace
+		*out = new(RegexMatchReplace)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtendedHTTPPathModifier.
+func (in *ExtendedHTTPPathModifier) DeepCopy() *ExtendedHTTPPathModifier {
+	if in == nil {
+		return nil
+	}
+	out := new(ExtendedHTTPPathModifier)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *FaultInjection) DeepCopyInto(out *FaultInjection) {
 	*out = *in
@@ -2296,6 +2317,21 @@ func (in *Redirect) DeepCopy() *Redirect {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RegexMatchReplace) DeepCopyInto(out *RegexMatchReplace) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RegexMatchReplace.
+func (in *RegexMatchReplace) DeepCopy() *RegexMatchReplace {
+	if in == nil {
+		return nil
+	}
+	out := new(RegexMatchReplace)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ResourceMetadata) DeepCopyInto(out *ResourceMetadata) {
 	*out = *in
@@ -3136,7 +3172,7 @@ func (in *URLRewrite) DeepCopyInto(out *URLRewrite) {
 	*out = *in
 	if in.Path != nil {
 		in, out := &in.Path, &out.Path
-		*out = new(HTTPPathModifier)
+		*out = new(ExtendedHTTPPathModifier)
 		(*in).DeepCopyInto(*out)
 	}
 	if in.Hostname != nil {
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 877723fa6..dac8f1780 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -148,6 +148,9 @@ type resourceMappings struct {
 	// The key is the namespaced name, group and kind of the filter and the value is the
 	// unstructured form of the resource.
 	extensionRefFilters map[utils.NamespacedNameWithGroupKind]unstructured.Unstructured
+	// httpRouteFilters is a map of HTTPRouteFilters, where the key is the namespaced name,
+	// group and kind of the HTTPFilter.
+	httpRouteFilters map[utils.NamespacedNameWithGroupKind]*egv1a1.HTTPRouteFilter
 }
 
 func newResourceMapping() *resourceMappings {
@@ -161,6 +164,7 @@ func newResourceMapping() *resourceMappings {
 		allAssociatedUDPRoutes:    sets.New[string](),
 		allAssociatedBackendRefs:  sets.New[gwapiv1.BackendObjectReference](),
 		extensionRefFilters:       map[utils.NamespacedNameWithGroupKind]unstructured.Unstructured{},
+		httpRouteFilters:          map[utils.NamespacedNameWithGroupKind]*egv1a1.HTTPRouteFilter{},
 	}
 }
 
@@ -1568,6 +1572,27 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
 		r.log.Info("Watching additional policy resource", "resource", gvk.String())
 	}
 
+	// Watch HTTPRouteFilter CRUDs and process affected HTTPRoute objects.
+	httpRouteFilter := []predicate.TypedPredicate[*egv1a1.HTTPRouteFilter]{
+		predicate.TypedGenerationChangedPredicate[*egv1a1.HTTPRouteFilter]{},
+		predicate.NewTypedPredicateFuncs[*egv1a1.HTTPRouteFilter](func(be *egv1a1.HTTPRouteFilter) bool {
+			return r.validateHTTPRouteFilterForReconcile(be)
+		}),
+	}
+	if r.namespaceLabel != nil {
+		httpRouteFilter = append(httpRouteFilter, predicate.NewTypedPredicateFuncs[*egv1a1.HTTPRouteFilter](func(be *egv1a1.HTTPRouteFilter) bool {
+			return r.hasMatchingNamespaceLabels(be)
+		}))
+	}
+	if err := c.Watch(
+		source.Kind(mgr.GetCache(), &egv1a1.HTTPRouteFilter{},
+			handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, be *egv1a1.HTTPRouteFilter) []reconcile.Request {
+				return r.enqueueClass(ctx, be)
+			}),
+			httpRouteFilter...)); err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/internal/provider/kubernetes/filters.go b/internal/provider/kubernetes/filters.go
index 985990fef..109c0314d 100644
--- a/internal/provider/kubernetes/filters.go
+++ b/internal/provider/kubernetes/filters.go
@@ -10,6 +10,8 @@ import (
 	"fmt"
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 )
 
 func (r *gatewayAPIReconciler) getExtensionRefFilters(ctx context.Context) ([]unstructured.Unstructured, error) {
@@ -43,3 +45,12 @@ func (r *gatewayAPIReconciler) getExtensionRefFilters(ctx context.Context) ([]un
 
 	return resourceItems, nil
 }
+
+func (r *gatewayAPIReconciler) getHTTPRouteFilters(ctx context.Context) ([]egv1a1.HTTPRouteFilter, error) {
+	httpFilterList := new(egv1a1.HTTPRouteFilterList)
+	if err := r.client.List(ctx, httpFilterList); err != nil {
+		return nil, fmt.Errorf("failed to list HTTPRouteFilters: %w", err)
+	}
+
+	return httpFilterList.Items, nil
+}
diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go
index 395b1c680..68a58dd87 100644
--- a/internal/provider/kubernetes/indexers.go
+++ b/internal/provider/kubernetes/indexers.go
@@ -45,6 +45,7 @@ const (
 	backendEnvoyProxyTelemetryIndex  = "backendEnvoyProxyTelemetryIndex"
 	secretEnvoyProxyIndex            = "secretEnvoyProxyIndex"
 	secretEnvoyExtensionPolicyIndex  = "secretEnvoyExtensionPolicyIndex"
+	httpRouteFilterHTTPRouteIndex    = "httpRouteFilterHTTPRouteIndex"
 )
 
 func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error {
@@ -73,6 +74,10 @@ func addHTTPRouteIndexers(ctx context.Context, mgr manager.Manager) error {
 		return err
 	}
 
+	if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1.HTTPRoute{}, httpRouteFilterHTTPRouteIndex, httpRouteFilterHTTPRouteIndexFunc); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -100,8 +105,6 @@ func backendHTTPRouteIndexFunc(rawObj client.Object) []string {
 	for _, rule := range httproute.Spec.Rules {
 		for _, backend := range rule.BackendRefs {
 			if backend.Kind == nil || string(*backend.Kind) == resource.KindService || string(*backend.Kind) == egv1a1.KindBackend {
-				// If an explicit Backend namespace is not provided, use the HTTPRoute namespace to
-				// lookup the provided Gateway Name.
 				backendRefs = append(backendRefs,
 					types.NamespacedName{
 						Namespace: gatewayapi.NamespaceDerefOr(backend.Namespace, httproute.Namespace),
@@ -114,6 +117,26 @@ func backendHTTPRouteIndexFunc(rawObj client.Object) []string {
 	return backendRefs
 }
 
+func httpRouteFilterHTTPRouteIndexFunc(rawObj client.Object) []string {
+	httproute := rawObj.(*gwapiv1.HTTPRoute)
+	var httpRouteFilterRefs []string
+	for _, rule := range httproute.Spec.Rules {
+		for _, filter := range rule.Filters {
+			if filter.ExtensionRef != nil && string(filter.ExtensionRef.Kind) == resource.KindHTTPRouteFilter {
+				// If an explicit Backend namespace is not provided, use the HTTPRoute namespace to
+				// lookup the provided Gateway Name.
+				httpRouteFilterRefs = append(httpRouteFilterRefs,
+					types.NamespacedName{
+						Namespace: httproute.Namespace,
+						Name:      string(filter.ExtensionRef.Name),
+					}.String(),
+				)
+			}
+		}
+	}
+	return httpRouteFilterRefs
+}
+
 func secretEnvoyProxyIndexFunc(rawObj client.Object) []string {
 	ep := rawObj.(*egv1a1.EnvoyProxy)
 	var secretReferences []string
diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go
index d14fceacb..9fb3fe86f 100644
--- a/internal/provider/kubernetes/predicates.go
+++ b/internal/provider/kubernetes/predicates.go
@@ -647,3 +647,29 @@ func (r *gatewayAPIReconciler) isExtensionPolicyReferencingSecret(nsName *types.
 
 	return len(eepList.Items) > 0
 }
+
+// isRouteReferencingHTTPRouteFilter returns true if the HTTPRouteFilter is referenced by an HTTPRoute
+func (r *gatewayAPIReconciler) isRouteReferencingHTTPRouteFilter(nsName *types.NamespacedName) bool {
+	ctx := context.Background()
+	httpRouteList := &gwapiv1.HTTPRouteList{}
+	if err := r.client.List(ctx, httpRouteList, &client.ListOptions{
+		FieldSelector: fields.OneTermEqualSelector(httpRouteFilterHTTPRouteIndex, nsName.String()),
+	}); err != nil {
+		r.log.Error(err, "unable to find associated HTTPRoutes")
+		return false
+	}
+
+	return len(httpRouteList.Items) != 0
+}
+
+// validateHTTPRouteFilterForReconcile tries finding the referencing HTTPRoute of the filter
+func (r *gatewayAPIReconciler) validateHTTPRouteFilterForReconcile(obj client.Object) bool {
+	hrf, ok := obj.(*egv1a1.HTTPRouteFilter)
+	if !ok {
+		r.log.Info("unexpected object type, bypassing reconciliation", "object", obj)
+		return false
+	}
+
+	nsName := utils.NamespacedName(hrf)
+	return r.isRouteReferencingHTTPRouteFilter(&nsName)
+}
diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go
index d298e7e0e..3a0a9f813 100644
--- a/internal/provider/kubernetes/routes.go
+++ b/internal/provider/kubernetes/routes.go
@@ -16,6 +16,7 @@ import (
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/gatewayapi"
 	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
 	"github.com/envoyproxy/gateway/internal/utils"
@@ -231,6 +232,15 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam
 ) error {
 	httpRouteList := &gwapiv1.HTTPRouteList{}
 
+	httpFilters, err := r.getHTTPRouteFilters(ctx)
+	if err != nil {
+		return err
+	}
+	for i := range httpFilters {
+		filter := httpFilters[i]
+		resourceMap.httpRouteFilters[utils.GetNamespacedNameWithGroupKind(&filter)] = &filter
+	}
+
 	extensionRefFilters, err := r.getExtensionRefFilters(ctx)
 	if err != nil {
 		return err
@@ -385,17 +395,29 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam
 							Kind:  string(filter.ExtensionRef.Kind),
 						},
 					}
-					extRefFilter, ok := resourceMap.extensionRefFilters[key]
-					if !ok {
-						r.log.Error(
-							errors.New("filter not found; bypassing rule"),
-							"Filter not found; bypassing rule",
-							"name", filter.ExtensionRef.Name,
-							"index", i)
-						continue
-					}
 
-					resourceTree.ExtensionRefFilters = append(resourceTree.ExtensionRefFilters, extRefFilter)
+					switch string(filter.ExtensionRef.Kind) {
+					case egv1a1.KindHTTPRouteFilter:
+						httpFilter, ok := resourceMap.httpRouteFilters[key]
+						if !ok {
+							r.log.Error(err, "HTTPRouteFilters not found; bypassing rule", "index", i)
+							continue
+						}
+
+						resourceTree.HTTPRouteFilters = append(resourceTree.HTTPRouteFilters, httpFilter)
+					default:
+						extRefFilter, ok := resourceMap.extensionRefFilters[key]
+						if !ok {
+							r.log.Error(
+								errors.New("filter not found; bypassing rule"),
+								"Filter not found; bypassing rule",
+								"name", filter.ExtensionRef.Name,
+								"index", i)
+							continue
+						}
+
+						resourceTree.ExtensionRefFilters = append(resourceTree.ExtensionRefFilters, extRefFilter)
+					}
 				}
 			}
 		}
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 42f17ff94..a8ec4a291 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -386,14 +386,15 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa
 	}
 
 	if urlRewrite.Path != nil {
-		if urlRewrite.Path.FullReplace != nil {
+		switch {
+		case urlRewrite.Path.FullReplace != nil:
 			routeAction.RegexRewrite = &matcherv3.RegexMatchAndSubstitute{
 				Pattern: &matcherv3.RegexMatcher{
 					Regex: "^/.*$",
 				},
 				Substitution: *urlRewrite.Path.FullReplace,
 			}
-		} else if urlRewrite.Path.PrefixMatchReplace != nil {
+		case urlRewrite.Path.PrefixMatchReplace != nil:
 			// Circumvent the case of "//" when the replace string is "/"
 			// An empty replace string does not seem to solve the issue so we are using
 			// a regex match and replace instead
@@ -406,6 +407,13 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa
 				// and the urlRewrite.Path.PrefixMatchReplace suffix with / the upstream will get unwanted /
 				routeAction.PrefixRewrite = strings.TrimSuffix(*urlRewrite.Path.PrefixMatchReplace, "/")
 			}
+		case urlRewrite.Path.RegexMatchReplace != nil:
+			routeAction.RegexRewrite = &matcherv3.RegexMatchAndSubstitute{
+				Pattern: &matcherv3.RegexMatcher{
+					Regex: urlRewrite.Path.RegexMatchReplace.Pattern,
+				},
+				Substitution: urlRewrite.Path.RegexMatchReplace.Substitution,
+			}
 		}
 	}
 
diff --git a/site/content/en/contributions/roadmap.md b/site/content/en/contributions/roadmap.md
index 955af2a96..54b808587 100644
--- a/site/content/en/contributions/roadmap.md
+++ b/site/content/en/contributions/roadmap.md
@@ -63,6 +63,10 @@ contributing to the project.
 - Add TrafficPolicy APIs for advanced features [Issue #1492][1492].
 - Envoy Gateway meets readiness criteria [Issue #1160][1160]. 
 
+### [v1.0.0][v1.0.0]: Envoy Gateway goes GA
+
+Visit the [milestones][milestones] page to learn more about the future roadmap.
+
 [issue]: https://github.com/envoyproxy/gateway/issues
 [meeting]: https://docs.google.com/document/d/1leqwsHX8N-XxNEyTflYjRur462ukFxd19Rnk3Uzy55I/edit?usp=sharing
 [pr]: https://github.com/envoyproxy/gateway/compare
@@ -72,6 +76,7 @@ contributing to the project.
 [v0.4.0]: https://github.com/envoyproxy/gateway/milestone/12
 [v0.5.0]: https://github.com/envoyproxy/gateway/milestone/13
 [v0.6.0]: https://github.com/envoyproxy/gateway/milestone/15
+[v1.0.0]: https://github.com/envoyproxy/gateway/milestone/18
 [17]: https://github.com/envoyproxy/gateway/issues/17
 [20]: https://github.com/envoyproxy/gateway/issues/20
 [24]: https://github.com/envoyproxy/gateway/issues/24
diff --git a/site/content/en/docs/tasks/security/tls-cert-manager.md b/site/content/en/docs/tasks/security/tls-cert-manager.md
index d51fa469e..d2b217d05 100644
--- a/site/content/en/docs/tasks/security/tls-cert-manager.md
+++ b/site/content/en/docs/tasks/security/tls-cert-manager.md
@@ -76,7 +76,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -92,7 +92,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/site/content/en/news/releases/_index.md b/site/content/en/news/releases/_index.md
index e8282654b..367fb17d2 100644
--- a/site/content/en/news/releases/_index.md
+++ b/site/content/en/news/releases/_index.md
@@ -8,8 +8,7 @@ type = "docs"
 +++
 
 This document provides details for Envoy Gateway releases. Envoy Gateway follows the Semantic Versioning [v2.0.0 spec][]
-for release versioning. Since Envoy Gateway is a new project, minor releases are the only defined releases. Envoy
-Gateway maintainers will establish additional release details, e.g. patch releases, at a future date.
+for release versioning.
 
 ## Stable Releases
 
diff --git a/site/content/en/v0.5/user/tls-cert-manager.md b/site/content/en/v0.5/user/tls-cert-manager.md
index 2be531c3c..67fbca574 100644
--- a/site/content/en/v0.5/user/tls-cert-manager.md
+++ b/site/content/en/v0.5/user/tls-cert-manager.md
@@ -76,7 +76,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -92,7 +92,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/site/content/en/v0.6/user/tls-cert-manager.md b/site/content/en/v0.6/user/tls-cert-manager.md
index aeb5e1a07..af88ecb71 100644
--- a/site/content/en/v0.6/user/tls-cert-manager.md
+++ b/site/content/en/v0.6/user/tls-cert-manager.md
@@ -76,7 +76,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -92,7 +92,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/site/content/en/v1.0/tasks/security/tls-cert-manager.md b/site/content/en/v1.0/tasks/security/tls-cert-manager.md
index 07aaf5b4a..56f8e14aa 100644
--- a/site/content/en/v1.0/tasks/security/tls-cert-manager.md
+++ b/site/content/en/v1.0/tasks/security/tls-cert-manager.md
@@ -76,7 +76,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -92,7 +92,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/site/content/en/v1.1/tasks/security/tls-cert-manager.md b/site/content/en/v1.1/tasks/security/tls-cert-manager.md
index d51fa469e..d2b217d05 100644
--- a/site/content/en/v1.1/tasks/security/tls-cert-manager.md
+++ b/site/content/en/v1.1/tasks/security/tls-cert-manager.md
@@ -76,7 +76,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -92,7 +92,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/tools/docker/envoy-gateway/Dockerfile b/tools/docker/envoy-gateway/Dockerfile
index 73eece8b9..2f30eec54 100644
--- a/tools/docker/envoy-gateway/Dockerfile
+++ b/tools/docker/envoy-gateway/Dockerfile
@@ -4,7 +4,7 @@ RUN mkdir -p /var/lib/eg
 
 # Use distroless as minimal base image to package the manager binary
 # Refer to https://github.com/GoogleContainerTools/distroless for more details
-FROM gcr.io/distroless/static:nonroot@sha256:42d15c647a762d3ce3a67eab394220f5268915d6ddba9006871e16e4698c3a24
+FROM gcr.io/distroless/static:nonroot@sha256:dcd3f1f09adef5689088c9c4d96a8d98c889d8281d3946145074f89eafe7e1af
 ARG TARGETPLATFORM
 COPY $TARGETPLATFORM/envoy-gateway /usr/local/bin/
 COPY --from=source --chown=65532:65532 /var/lib /var/lib
diff --git a/tools/hack/create-cluster.sh b/tools/hack/create-cluster.sh
index 16e1477b5..ad3cff1cd 100755
--- a/tools/hack/create-cluster.sh
+++ b/tools/hack/create-cluster.sh
@@ -7,11 +7,13 @@ CLUSTER_NAME=${CLUSTER_NAME:-"envoy-gateway"}
 METALLB_VERSION=${METALLB_VERSION:-"v0.13.10"}
 KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.31.0"}
 NUM_WORKERS=${NUM_WORKERS:-""}
-
+IP_FAMILY=${IP_FAMILY:-"ipv4"}
 
 KIND_CFG=$(cat <<-EOM
 kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
+networking:
+  ipFamily: ${IP_FAMILY}
 nodes:
 - role: control-plane
 EOM
@@ -53,10 +55,31 @@ kubectl rollout status -n metallb-system deployment/controller --timeout 5m
 kubectl rollout status -n metallb-system daemonset/speaker --timeout 5m
 
 # Apply config with addresses based on docker network IPAM.
-subnet=$(docker network inspect kind | jq -r '.[].IPAM.Config[].Subnet | select(contains(":") | not)')
-# Assume default kind network subnet prefix of 16, and choose addresses in that range.
-address_first_octets=$(echo "${subnet}" | awk -F. '{printf "%s.%s",$1,$2}')
-address_range="${address_first_octets}.255.200-${address_first_octets}.255.250"
+address_ranges=""
+
+if [ "${IP_FAMILY}" = "ipv4" ] || [ "${IP_FAMILY}" = "dual" ]; then
+    subnet_v4=$(docker network inspect kind | jq -r '.[].IPAM.Config[] | select(.Subnet | contains(":") | not) | .Subnet')
+    address_prefix_v4=$(echo "${subnet_v4}" | awk -F. '{print $1"."$2"."$3}')
+    address_range_v4="${address_prefix_v4}.200-${address_prefix_v4}.250"
+    echo "IPv4 address range: ${address_range_v4}"
+    address_ranges+="- ${address_range_v4}"
+fi
+
+if [ "${IP_FAMILY}" = "ipv6" ] || [ "${IP_FAMILY}" = "dual" ]; then
+    subnet_v6=$(docker network inspect kind | jq -r '.[].IPAM.Config[] | select(.Subnet | contains(":")) | .Subnet')
+    ipv6_prefix="${subnet_v6%::*}"
+    address_range_v6="${ipv6_prefix}::c8-${ipv6_prefix}::fa"
+    echo "IPv6 address range: ${address_range_v6}"
+    [ -n "${address_ranges}" ] && address_ranges+="\n"
+    address_ranges+="- ${address_range_v6}"
+fi
+
+if [ -z "${address_ranges}" ]; then
+    echo "Error: No valid IP ranges found for IP_FAMILY=${IP_FAMILY}"
+    exit 1
+fi
+
+# Apply MetalLB IPAddressPool and L2Advertisement
 kubectl apply -f - <<EOF
 apiVersion: metallb.io/v1beta1
 kind: IPAddressPool
@@ -65,7 +88,7 @@ metadata:
   name: kube-services
 spec:
   addresses:
-  - ${address_range}
+$(echo -e "${address_ranges}" | sed 's/^/    /')
 ---
 apiVersion: metallb.io/v1beta1
 kind: L2Advertisement
diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod
index e719faa38..7dcf60d5c 100644
--- a/tools/src/buf/go.mod
+++ b/tools/src/buf/go.mod
@@ -2,7 +2,7 @@ module local
 
 go 1.23.1
 
-require github.com/bufbuild/buf v1.41.0
+require github.com/bufbuild/buf v1.42.0
 
 require (
 	buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.34.2-20240904181154-a0be11449112.2 // indirect
@@ -11,6 +11,7 @@ require (
 	buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240821192916-45ba72cdd479.2 // indirect
 	buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.34.2-20240828222655-5345c0a56177.2 // indirect
 	buf.build/go/bufplugin v0.2.0 // indirect
+	buf.build/go/protoyaml v0.2.0 // indirect
 	connectrpc.com/connect v1.16.2 // indirect
 	connectrpc.com/otelconnect v0.7.1 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
@@ -20,7 +21,6 @@ require (
 	github.com/bufbuild/protocompile v0.14.1 // indirect
 	github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a // indirect
 	github.com/bufbuild/protovalidate-go v0.6.5 // indirect
-	github.com/bufbuild/protoyaml-go v0.1.12 // indirect
 	github.com/containerd/cgroups/v3 v3.0.3 // indirect
 	github.com/containerd/containerd v1.7.22 // indirect
 	github.com/containerd/continuity v0.4.3 // indirect
@@ -78,6 +78,7 @@ require (
 	github.com/pkg/profile v1.7.0 // indirect
 	github.com/quic-go/qpack v0.5.1 // indirect
 	github.com/quic-go/quic-go v0.47.0 // indirect
+	github.com/rogpeppe/go-internal v1.10.0 // indirect
 	github.com/rs/cors v1.11.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
@@ -86,7 +87,7 @@ require (
 	github.com/stoewer/go-strcase v1.3.0 // indirect
 	github.com/vbatts/tar-split v0.11.5 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
 	go.opentelemetry.io/otel v1.30.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect
 	go.opentelemetry.io/otel/metric v1.30.0 // indirect
@@ -107,7 +108,7 @@ require (
 	golang.org/x/tools v0.25.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
-	google.golang.org/grpc v1.66.1 // indirect
+	google.golang.org/grpc v1.66.2 // indirect
 	google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	pluginrpc.com/pluginrpc v0.3.0 // indirect
diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum
index 49fa12e2c..e76ce65da 100644
--- a/tools/src/buf/go.sum
+++ b/tools/src/buf/go.sum
@@ -10,6 +10,8 @@ buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.34.2-20240828222655-5
 buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.34.2-20240828222655-5345c0a56177.2/go.mod h1:GjH0gjlY/ns16X8d6eaXV2W+6IFwsO5Ly9WVnzyd1E0=
 buf.build/go/bufplugin v0.2.0 h1:nnNvWzUgQXitRDmjWWIkuXj9klreAAE94sVCsL+0v5g=
 buf.build/go/bufplugin v0.2.0/go.mod h1:ZZYGt6PDcjbBSywdz/G8NdXkIuWi2rzR8CduGzbCPdk=
+buf.build/go/protoyaml v0.2.0 h1:2g3OHjtLDqXBREIOjpZGHmQ+U/4mkN1YiQjxNB68Ip8=
+buf.build/go/protoyaml v0.2.0/go.mod h1:L/9QvTDkTWcDTzAL6HMfN+mYC6CmZRm2KnsUA054iL0=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
 connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
@@ -26,16 +28,14 @@ github.com/Microsoft/hcsshim v0.12.6 h1:qEnZjoHXv+4/s0LmKZWE0/AiZmMWEIkFfWBSf1a0
 github.com/Microsoft/hcsshim v0.12.6/go.mod h1:ZABCLVcvLMjIkzr9rUGcQ1QA0p0P3Ps+d3N1g2DsFfk=
 github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
 github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
-github.com/bufbuild/buf v1.41.0 h1:4z4r7bysUZ8EAseRjQ/AWQNpOP3M1v15N+vhLe8U1WU=
-github.com/bufbuild/buf v1.41.0/go.mod h1:4fMd9FF67dUbuzMFg5rIJy+6Iu6oKldAG2Z9A8zAlEk=
+github.com/bufbuild/buf v1.42.0 h1:awh6dZ1qjC468Wq9FN/3WAeBLoe6LV8GeFSHlL3ltvE=
+github.com/bufbuild/buf v1.42.0/go.mod h1:AKKOqvsTLjeFXTEc7h9KsxsVBY/hETalmy7k8e0FVEo=
 github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
 github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
 github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a h1:l3RhVoG0RtC61h6TVWnkniGj4TgBebuyPQRdleFAmTg=
 github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
 github.com/bufbuild/protovalidate-go v0.6.5 h1:WucDKXIbK22WjkO8A8J6Yyxxy0jl91Oe9LSMduq3YEE=
 github.com/bufbuild/protovalidate-go v0.6.5/go.mod h1:LHDiGCWSM3GagZEnyEZ1sPtFwi6Ja4tVTi/DCc+iDFI=
-github.com/bufbuild/protoyaml-go v0.1.12 h1:tIJrwvGxumVpNwLsw/AevT1QnkPDBuAObBSuBAdmAWY=
-github.com/bufbuild/protoyaml-go v0.1.12/go.mod h1:Xmz3wct+08Va+g9gjIuLTAmxW2w6sre5Wrgw7K3gn0I=
 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -132,8 +132,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
 github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -161,8 +159,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ=
 github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8=
-github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
-github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
+github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88=
+github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -228,8 +226,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
 github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
 github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
 github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
 github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -257,8 +255,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
 go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
 go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
@@ -367,8 +365,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
-google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
+google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=


Test patch:
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.in.yaml
new file mode 100644
index 000000000..fd0fea29f
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.in.yaml
@@ -0,0 +1,147 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      hostname: "*.envoyproxy.io"
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-1
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/valid"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-2
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/host-and-regex-path"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: URLRewrite
+        urlRewrite:
+          hostname: "rewrite.com"
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-3
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/regex-path-and-host"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+      - type: URLRewrite
+        urlRewrite:
+          hostname: "rewrite.com"
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-missing-substitution
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/missing-substitution"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: missing-substitution
+httpFilters:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: valid
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          pattern: '.*'
+          substitution: foo
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: missing-substitution
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          pattern: '.*'
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml
new file mode 100644
index 000000000..5c8d2527a
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-http.out.yaml
@@ -0,0 +1,366 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      hostname: '*.envoyproxy.io'
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 4
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /valid
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-2
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: URLRewrite
+        urlRewrite:
+          hostname: rewrite.com
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /host-and-regex-path
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-3
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      - type: URLRewrite
+        urlRewrite:
+          hostname: rewrite.com
+      matches:
+      - path:
+          value: /regex-path-and-host
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-missing-substitution
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: missing-substitution
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /missing-substitution
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*.envoyproxy.io'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-missing-substitution/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-missing-substitution
+          namespace: default
+        name: httproute/default/httproute-missing-substitution/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /missing-substitution
+        urlRewrite:
+          path:
+            fullReplace: null
+            prefixMatchReplace: null
+            regexMatchReplace:
+              pattern: .*
+              substitution: ""
+      - destination:
+          name: httproute/default/httproute-2/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-2
+          namespace: default
+        name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /host-and-regex-path
+        urlRewrite:
+          hostname: rewrite.com
+          path:
+            fullReplace: null
+            prefixMatchReplace: null
+            regexMatchReplace:
+              pattern: .*
+              substitution: foo
+      - destination:
+          name: httproute/default/httproute-3/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-3
+          namespace: default
+        name: httproute/default/httproute-3/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /regex-path-and-host
+        urlRewrite:
+          hostname: rewrite.com
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /valid
+        urlRewrite:
+          path:
+            fullReplace: null
+            prefixMatchReplace: null
+            regexMatchReplace:
+              pattern: .*
+              substitution: foo
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.in.yaml
new file mode 100644
index 000000000..1ad75975b
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.in.yaml
@@ -0,0 +1,230 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    namespace: envoy-gateway
+    name: gateway-1
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      hostname: "*.envoyproxy.io"
+      allowedRoutes:
+        namespaces:
+          from: All
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-invalid-pattern
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/invalid-pattern"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: invalid-pattern
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-missing-pattern
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/missing-pattern"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: missing-pattern
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-multiple-path-rewrites-1
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/ext-first"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+      - type: URLRewrite
+        urlRewrite:
+          path:
+            type: ReplacePrefixMatch
+            replacePrefixMatch: /rewrite
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-multiple-path-rewrites-2
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/inline-first"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: URLRewrite
+        urlRewrite:
+          path:
+            type: ReplacePrefixMatch
+            replacePrefixMatch: /rewrite
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-multiple-regex-path-rewrites
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/two-regex"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid-2
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    namespace: default
+    name: httproute-not-found
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - namespace: envoy-gateway
+      name: gateway-1
+      sectionName: http
+    rules:
+    - matches:
+      - path:
+          value: "/notfound"
+      backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: ExtensionRef
+        extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: notfound
+httpFilters:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: valid
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          pattern: '.*'
+          substitution: foo
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: valid-2
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          pattern: '.*'
+          substitution: foo
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: invalid-pattern
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          pattern: '"([a-z]+)"*+?'
+          substitution: foo
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: HTTPRouteFilter
+  metadata:
+    name: missing-pattern
+    namespace: default
+  spec:
+    urlRewrite:
+      path:
+        type: ReplaceRegexMatch
+        replaceRegexMatch:
+          substitution: foo
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml
new file mode 100644
index 000000000..fb9e85a63
--- /dev/null
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-filter-regex-match-replace-invalid.out.yaml
@@ -0,0 +1,383 @@
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      hostname: '*.envoyproxy.io'
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 6
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-invalid-pattern
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: invalid-pattern
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /invalid-pattern
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: ReplaceRegexMatch must be a valid RE2 regular expression
+        reason: UnsupportedValue
+        status: "False"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-missing-pattern
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: missing-pattern
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /missing-pattern
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: ReplaceRegexMatch Pattern must be set when rewrite path type is "ReplaceRegexMatch"
+        reason: UnsupportedValue
+        status: "False"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-multiple-path-rewrites-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      - type: URLRewrite
+        urlRewrite:
+          path:
+            replacePrefixMatch: /rewrite
+            type: ReplacePrefixMatch
+      matches:
+      - path:
+          value: /ext-first
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-multiple-path-rewrites-2
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - type: URLRewrite
+        urlRewrite:
+          path:
+            replacePrefixMatch: /rewrite
+            type: ReplacePrefixMatch
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /inline-first
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule
+        reason: UnsupportedValue
+        status: "False"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-multiple-regex-path-rewrites
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid
+        type: ExtensionRef
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: valid-2
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /two-regex
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Cannot configure multiple urlRewrite filters for a single HTTPRouteRule
+        reason: UnsupportedValue
+        status: "False"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-not-found
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      filters:
+      - extensionRef:
+          group: gateway.envoyproxy.io
+          kind: HTTPRouteFilter
+          name: notfound
+        type: ExtensionRef
+      matches:
+      - path:
+          value: /notfound
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: 'Unable to translate HTTPRouteFilter: default/notfound'
+        reason: UnsupportedValue
+        status: "False"
+        type: Accepted
+      - lastTransitionTime: null
+        message: 'Unable to translate HTTPRouteFilter: default/notfound'
+        reason: BackendNotFound
+        status: "False"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*.envoyproxy.io'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-multiple-path-rewrites-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-multiple-path-rewrites-1
+          namespace: default
+        name: httproute/default/httproute-multiple-path-rewrites-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /ext-first
+        urlRewrite:
+          path:
+            fullReplace: null
+            prefixMatchReplace: /rewrite
diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go
index 882aa090e..14b624f22 100644
--- a/internal/ir/xds_test.go
+++ b/internal/ir/xds_test.go
@@ -309,8 +309,10 @@ var (
 		},
 		URLRewrite: &URLRewrite{
 			Hostname: ptr.To("rewrite.example.com"),
-			Path: &HTTPPathModifier{
-				FullReplace: ptr.To("/rewrite"),
+			Path: &ExtendedHTTPPathModifier{
+				HTTPPathModifier: HTTPPathModifier{
+					FullReplace: ptr.To("/rewrite"),
+				},
 			},
 		},
 	}
@@ -323,9 +325,11 @@ var (
 		},
 		URLRewrite: &URLRewrite{
 			Hostname: ptr.To("rewrite.example.com"),
-			Path: &HTTPPathModifier{
-				FullReplace:        ptr.To("/rewrite"),
-				PrefixMatchReplace: ptr.To("/rewrite"),
+			Path: &ExtendedHTTPPathModifier{
+				HTTPPathModifier: HTTPPathModifier{
+					FullReplace:        ptr.To("/rewrite"),
+					PrefixMatchReplace: ptr.To("/rewrite"),
+				},
 			},
 		},
 	}
diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go
index d673def5c..669a790af 100644
--- a/internal/provider/kubernetes/kubernetes_test.go
+++ b/internal/provider/kubernetes/kubernetes_test.go
@@ -1430,26 +1430,20 @@ func TestNamespaceSelectorProvider(t *testing.T) {
 		require.NoError(t, cli.Delete(ctx, nonWatchedSvc))
 	}()
 
-	watchedHTTPRoute := test.GetHTTPRoute(
-		types.NamespacedName{
-			Namespace: watchedNS.Name,
-			Name:      "watched-http-route",
-		},
-		watchedGateway.Name,
-		types.NamespacedName{Name: watchedSvc.Name}, 80)
+	watchedHTTPRoute := test.GetHTTPRoute(types.NamespacedName{
+		Namespace: watchedNS.Name,
+		Name:      "watched-http-route",
+	}, watchedGateway.Name, types.NamespacedName{Name: watchedSvc.Name}, 80, "")
 
 	require.NoError(t, cli.Create(ctx, watchedHTTPRoute))
 	defer func() {
 		require.NoError(t, cli.Delete(ctx, watchedHTTPRoute))
 	}()
 
-	nonWatchedHTTPRoute := test.GetHTTPRoute(
-		types.NamespacedName{
-			Namespace: nonWatchedNS.Name,
-			Name:      "non-watched-http-route",
-		},
-		nonWatchedGateway.Name,
-		types.NamespacedName{Name: nonWatchedSvc.Name}, 8001)
+	nonWatchedHTTPRoute := test.GetHTTPRoute(types.NamespacedName{
+		Namespace: nonWatchedNS.Name,
+		Name:      "non-watched-http-route",
+	}, nonWatchedGateway.Name, types.NamespacedName{Name: nonWatchedSvc.Name}, 8001, "")
 	require.NoError(t, cli.Create(ctx, nonWatchedHTTPRoute))
 	defer func() {
 		require.NoError(t, cli.Delete(ctx, nonWatchedHTTPRoute))
diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go
index a442ddcf2..61a09ffb8 100644
--- a/internal/provider/kubernetes/predicates_test.go
+++ b/internal/provider/kubernetes/predicates_test.go
@@ -124,15 +124,10 @@ func TestGatewayClassHasMatchingNamespaceLabels(t *testing.T) {
 		}
 		t.Run(tc.name, func(t *testing.T) {
 			res := r.hasMatchingNamespaceLabels(
-				test.GetHTTPRoute(
-					types.NamespacedName{
-						Namespace: ns,
-						Name:      "httproute-test",
-					},
-					"scheduled-status-test",
-					types.NamespacedName{Name: "service"},
-					80,
-				))
+				test.GetHTTPRoute(types.NamespacedName{
+					Namespace: ns,
+					Name:      "httproute-test",
+				}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""))
 			require.Equal(t, tc.expect, res)
 		})
 	}
@@ -405,7 +400,7 @@ func TestValidateEndpointSliceForReconcile(t *testing.T) {
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
 			},
 			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "other-service"),
 			expect:        false,
@@ -415,7 +410,7 @@ func TestValidateEndpointSliceForReconcile(t *testing.T) {
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
 			},
 			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service"),
 			expect:        true,
@@ -570,7 +565,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
 			},
 			service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
 			expect:  true,
@@ -582,7 +577,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			name: "route service routes exist but with non-existing gateway reference",
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
 			},
 			service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
 			expect:  true,
@@ -966,18 +961,13 @@ func TestCheckObjectNamespaceLabels(t *testing.T) {
 	}{
 		{
 			name: "matching labels of namespace of the object is a subset of namespaceLabels",
-			object: test.GetHTTPRoute(
-				types.NamespacedName{
-					Name:      "foo-route",
-					Namespace: "foo",
-				},
-				"eg",
-				types.NamespacedName{
-					Name:      "foo-svc",
-					Namespace: "foo",
-				},
-				8080,
-			),
+			object: test.GetHTTPRoute(types.NamespacedName{
+				Name:      "foo-route",
+				Namespace: "foo",
+			}, "eg", types.NamespacedName{
+				Name:      "foo-svc",
+				Namespace: "foo",
+			}, 8080, ""),
 			ns: &corev1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: "foo",
@@ -991,18 +981,13 @@ func TestCheckObjectNamespaceLabels(t *testing.T) {
 		},
 		{
 			name: "non-matching labels of namespace of the object is a subset of namespaceLabels",
-			object: test.GetHTTPRoute(
-				types.NamespacedName{
-					Name:      "bar-route",
-					Namespace: "bar",
-				},
-				"eg",
-				types.NamespacedName{
-					Name:      "bar-svc",
-					Namespace: "bar",
-				},
-				8080,
-			),
+			object: test.GetHTTPRoute(types.NamespacedName{
+				Name:      "bar-route",
+				Namespace: "bar",
+			}, "eg", types.NamespacedName{
+				Name:      "bar-svc",
+				Namespace: "bar",
+			}, 8080, ""),
 			ns: &corev1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: "bar",
@@ -1149,3 +1134,64 @@ func TestMatchLabelsAndExpressions(t *testing.T) {
 		})
 	}
 }
+
+// TestValidateHTTPRouteFilerForReconcile tests the vlidateHTTPRouteFilerForReconcile
+// predicate function.
+func TestValidateHTTPRouteFilerForReconcile(t *testing.T) {
+	sampleGWC := test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil)
+	sampleGateway := test.GetGateway(types.NamespacedName{Namespace: "default", Name: "scheduled-status-test"}, "test-gc", 8080)
+	sampleService := test.GetService(types.NamespacedName{Name: "service"}, nil, nil)
+	sampleHTTPRouteFilter := test.GetHTTPRouteFilter(types.NamespacedName{Name: "httproutefilter"})
+
+	testCases := []struct {
+		name            string
+		configs         []client.Object
+		httpRouteFilter client.Object
+		expect          bool
+	}{
+		{
+			name: "httproutefilter but not referenced by route",
+			configs: []client.Object{
+				sampleGWC,
+				sampleGateway,
+				sampleService,
+				sampleHTTPRouteFilter,
+			},
+			httpRouteFilter: sampleHTTPRouteFilter,
+			expect:          false,
+		},
+		{
+			name: "httproutefitler referenced by route",
+			configs: []client.Object{
+				sampleGWC,
+				sampleGateway,
+				sampleService,
+				sampleHTTPRouteFilter,
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, "httproutefilter"),
+			},
+			httpRouteFilter: sampleHTTPRouteFilter,
+			expect:          true,
+		},
+	}
+
+	// Create the reconciler.
+	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+
+	r := gatewayAPIReconciler{
+		classController: egv1a1.GatewayControllerName,
+		log:             logger,
+	}
+
+	for _, tc := range testCases {
+		r.client = fakeclient.NewClientBuilder().
+			WithScheme(envoygateway.GetScheme()).
+			WithObjects(tc.configs...).
+			WithIndex(&gwapiv1.HTTPRoute{}, backendHTTPRouteIndex, backendHTTPRouteIndexFunc).
+			WithIndex(&gwapiv1.HTTPRoute{}, httpRouteFilterHTTPRouteIndex, httpRouteFilterHTTPRouteIndexFunc).
+			Build()
+		t.Run(tc.name, func(t *testing.T) {
+			res := r.validateHTTPRouteFilterForReconcile(tc.httpRouteFilter)
+			require.Equal(t, tc.expect, res)
+		})
+	}
+}
diff --git a/internal/provider/kubernetes/test/utils.go b/internal/provider/kubernetes/test/utils.go
index ae798b57d..6fe50fa75 100644
--- a/internal/provider/kubernetes/test/utils.go
+++ b/internal/provider/kubernetes/test/utils.go
@@ -107,8 +107,8 @@ func GetSecret(nsName types.NamespacedName) *corev1.Secret {
 }
 
 // GetHTTPRoute returns a sample HTTPRoute with a parent reference.
-func GetHTTPRoute(nsName types.NamespacedName, parent string, serviceName types.NamespacedName, port int32) *gwapiv1.HTTPRoute {
-	return &gwapiv1.HTTPRoute{
+func GetHTTPRoute(nsName types.NamespacedName, parent string, serviceName types.NamespacedName, port int32, httpRouteFilterName string) *gwapiv1.HTTPRoute {
+	httpRoute := &gwapiv1.HTTPRoute{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: nsName.Namespace,
 			Name:      nsName.Name,
@@ -135,6 +135,21 @@ func GetHTTPRoute(nsName types.NamespacedName, parent string, serviceName types.
 			},
 		},
 	}
+
+	if httpRouteFilterName != "" {
+		httpRoute.Spec.Rules[0].Filters = []gwapiv1.HTTPRouteFilter{
+			{
+				Type: gwapiv1.HTTPRouteFilterExtensionRef,
+				ExtensionRef: &gwapiv1.LocalObjectReference{
+					Group: egv1a1.GroupName,
+					Kind:  egv1a1.KindHTTPRouteFilter,
+					Name:  gwapiv1.ObjectName(httpRouteFilterName),
+				},
+			},
+		}
+	}
+
+	return httpRoute
 }
 
 // GetGRPCRoute returns a sample GRPCRoute with a parent reference.
@@ -329,3 +344,24 @@ func GetEndpointSlice(nsName types.NamespacedName, svcName string) *discoveryv1.
 		},
 	}
 }
+
+// GetHTTPRouteFilter returns a sample Service with labels and ports.
+func GetHTTPRouteFilter(nsName types.NamespacedName) *egv1a1.HTTPRouteFilter {
+	return &egv1a1.HTTPRouteFilter{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      nsName.Name,
+			Namespace: nsName.Namespace,
+		},
+		Spec: egv1a1.HTTPRouteFilterSpec{
+			URLRewrite: &egv1a1.HTTPURLRewriteFilter{
+				Path: &egv1a1.HTTPPathModifier{
+					Type: egv1a1.RegexHTTPPathModifier,
+					ReplaceRegexMatch: &egv1a1.ReplaceRegexMatch{
+						Pattern:      "foo",
+						Substitution: "bar",
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-regex.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-regex.yaml
new file mode 100644
index 000000000..038920118
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-url-regex.yaml
@@ -0,0 +1,26 @@
+name: "http-route"
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "rewrite-route"
+    pathMatch:
+      prefix: "/origin"
+    hostname: gateway.envoyproxy.io
+    destination:
+      name: "rewrite-route"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+    urlRewrite:
+      path:
+        regexMatchReplace:
+          pattern: '^/service/([^/]+)(/.*)$'
+          substitution: '\2/instance/\1'
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.clusters.yaml
new file mode 100644
index 000000000..3a2b7308d
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: rewrite-route
+  lbPolicy: LEAST_REQUEST
+  name: rewrite-route
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.endpoints.yaml
new file mode 100644
index 000000000..ca1ef21c9
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: rewrite-route
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: rewrite-route/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.listeners.yaml
new file mode 100644
index 000000000..c3fb11301
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.listeners.yaml
@@ -0,0 +1,34 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: first-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: first-listener
+  name: first-listener
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.routes.yaml
new file mode 100644
index 000000000..20d4e99ef
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-regex.routes.yaml
@@ -0,0 +1,18 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    name: first-listener/gateway_envoyproxy_io
+    routes:
+    - match:
+        pathSeparatedPrefix: /origin
+      name: rewrite-route
+      route:
+        cluster: rewrite-route
+        regexRewrite:
+          pattern:
+            regex: ^/service/([^/]+)(/.*)$
+          substitution: \2/instance/\1
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index d02a16861..b716d7ad3 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1905,7 +1905,7 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `type` | _[HTTPPathModifierType](#httppathmodifiertype)_ |  true  |  |
-| `replaceRegexMatch` | _[ReplaceRegexMatch](#replaceregexmatch)_ |  true  | ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite<br />Some examples:<br />(1) replaceRegexMatch:<br />      pattern: ^/service/([^/]+)(/.*)$<br />      substitution: \2/instance/\1<br />    Would transform /service/foo/v1/api into /v1/api/instance/foo.<br />(2) replaceRegexMatch:<br />      pattern: one<br />      substitution: two<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz.<br />(3) replaceRegexMatch:<br />      pattern: ^(.*?)one(.*)$<br />      substitution: \1two\2<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz.<br />(3) replaceRegexMatch:<br />      pattern: (?i)/xxx/<br />      substitution: /yyy/<br />    Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). |
+| `replaceRegexMatch` | _[ReplaceRegexMatch](#replaceregexmatch)_ |  false  | ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite<br />Some examples:<br />(1) replaceRegexMatch:<br />      pattern: ^/service/([^/]+)(/.*)$<br />      substitution: \2/instance/\1<br />    Would transform /service/foo/v1/api into /v1/api/instance/foo.<br />(2) replaceRegexMatch:<br />      pattern: one<br />      substitution: two<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz.<br />(3) replaceRegexMatch:<br />      pattern: ^(.*?)one(.*)$<br />      substitution: \1two\2<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz.<br />(3) replaceRegexMatch:<br />      pattern: (?i)/xxx/<br />      substitution: /yyy/<br />    Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). |
 
 
 #### HTTPPathModifierType
diff --git a/site/content/en/latest/tasks/security/tls-cert-manager.md b/site/content/en/latest/tasks/security/tls-cert-manager.md
index 997a7ef25..61ebb5c01 100644
--- a/site/content/en/latest/tasks/security/tls-cert-manager.md
+++ b/site/content/en/latest/tasks/security/tls-cert-manager.md
@@ -75,7 +75,7 @@ EOF
 We now have to patch the example Gateway to reference cert-manager:
 
 ```console
-$ kubectl patch gateway/eg --patch-file - <<EOF
+$ kubectl patch gateway/eg --patch '
 metadata:
   annotations:
     cert-manager.io/cluster-issuer: selfsigned
@@ -91,7 +91,7 @@ spec:
       certificateRefs:
       - kind: Secret
         name: eg-https
-EOF
+' --type=merge
 ```
 
 You could instead create a new Gateway serving HTTPS, if you'd prefer.
diff --git a/site/content/en/latest/tasks/traffic/http-urlrewrite.md b/site/content/en/latest/tasks/traffic/http-urlrewrite.md
index 67915f93f..a643d775a 100644
--- a/site/content/en/latest/tasks/traffic/http-urlrewrite.md
+++ b/site/content/en/latest/tasks/traffic/http-urlrewrite.md
@@ -275,6 +275,160 @@ $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}/get
 You can see that the `X-Envoy-Original-Path` is `/get/origin/path/extra`, but the actual path is
 `/force/replace/fullpath`.
 
+## Rewrite URL Path with Regex
+
+In addition to core Gateway-API rewrite options, Envoy Gateway supports extended rewrite options through the [HTTPRouteFilter][] API.
+The `HTTPRouteFilter` API can be configured to use [RE2][]-compatible regex matchers and substitutions to rewrite a portion of the url. 
+In the example below, requests sent to `http://${GATEWAY_HOST}/service/xxx/yyy` (where `xxx` is a single path portion and `yyy` is one or more path portions) 
+are rewritten to `http://${GATEWAY_HOST}/yyy/instance/xxx`. The entire path is matched and rewritten using capture groups.
+
+{{< tabpane text=true >}}
+{{% tab header="Apply from stdin" %}}
+
+```shell
+cat <<EOF | kubectl apply -f -
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-filter-url-regex-rewrite
+spec:
+  parentRefs:
+    - name: eg
+  hostnames:
+    - path.regex.rewrite.example
+  rules:
+    - matches:
+      - path:
+          type: PathPrefix
+          value: "/"
+      filters:
+        - type: ExtensionRef
+          extensionRef:
+            group: gateway.envoyproxy.io
+            kind: HTTPRouteFilter
+            name: regex-path-rewrite
+      backendRefs:
+      - name: backend
+        port: 3000
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: HTTPRouteFilter
+metadata:
+  name: regex-path-rewrite
+spec:
+  urlRewrite:
+    path:
+      type: ReplaceRegexMatch
+      replaceRegexMatch:
+        pattern: '^/service/([^/]+)(/.*)$'
+        substitution: '\2/instance/\1'
+EOF
+```
+
+{{% /tab %}}
+{{% tab header="Apply from file" %}}
+Save and apply the following resource to your cluster:
+
+```yaml
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-filter-url-regex-rewrite
+spec:
+  parentRefs:
+    - name: eg
+  hostnames:
+    - path.regex.rewrite.example
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: "/"
+      filters:
+        - type: ExtensionRef
+          extensionRef:
+            group: gateway.envoyproxy.io
+            kind: HTTPRouteFilter
+            name: regex-path-rewrite
+      backendRefs:
+        - name: backend
+          port: 3000
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: HTTPRouteFilter
+metadata:
+  name: regex-path-rewrite
+spec:
+  urlRewrite:
+    path:
+      type: ReplaceRegexMatch
+      replaceRegexMatch:
+        pattern: '^/service/([^/]+)(/.*)$'
+        substitution: '\2/instance/\1'
+```
+
+{{% /tab %}}
+{{< /tabpane >}}
+
+The HTTPRoute status should indicate that it has been accepted and is bound to the example Gateway.
+
+```shell
+kubectl get httproute/http-filter-url-rewrite -o yaml
+```
+
+Querying `http://${GATEWAY_HOST}/get/origin/path/extra` should rewrite the request to
+`http://${GATEWAY_HOST}/force/replace/fullpath`.
+
+```console
+$ curl -L -vvv --header "Host: path.regex.rewrite.example" "http://${GATEWAY_HOST}/get/origin/path/extra"
+...
+> GET /service/foo/v1/api HTTP/1.1
+> Host: path.regex.rewrite.example
+> User-Agent: curl/8.7.1
+> Accept: */*
+>
+* Request completely sent off
+< HTTP/1.1 200 OK
+< content-type: application/json
+< x-content-type-options: nosniff
+< date: Mon, 16 Sep 2024 18:49:48 GMT
+< content-length: 482
+<
+{
+ "path": "/v1/api/instance/foo",
+ "host": "path.regex.rewrite.example",
+ "method": "GET",
+ "proto": "HTTP/1.1",
+ "headers": {
+  "Accept": [
+   "*/*"
+  ],
+  "User-Agent": [
+   "curl/8.7.1"
+  ],
+  "X-Envoy-Internal": [
+   "true"
+  ],
+  "X-Forwarded-For": [
+   "10.244.0.37"
+  ],
+  "X-Forwarded-Proto": [
+   "http"
+  ],
+  "X-Request-Id": [
+   "24a5958f-1bfa-4694-a9c1-807d5139a18a"
+  ]
+ },
+ "namespace": "default",
+ "ingress": "",
+ "service": "",
+ "pod": "backend-765694d47f-lzmpm"
+...
+```
+
+You can see that the path is rewritten from `/service/foo/v1/api`, to `/v1/api/instance/foo`.
+
 ## Rewrite Host Name
 
 You can configure to rewrite the hostname like below. In this example, any requests sent to
@@ -402,3 +556,5 @@ $ curl -L -vvv --header "Host: path.rewrite.example" "http://${GATEWAY_HOST}/get
 You can see that the `X-Forwarded-Host` is `path.rewrite.example`, but the actual host is `envoygateway.io`.
 
 [HTTPURLRewriteFilter]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPURLRewriteFilter
+[HTTPRouteFilter]: ../../../api/extension_types#httproutefilter
+[RE2]: https://github.com/google/re2/wiki/Syntax
\ No newline at end of file
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index d02a16861..b716d7ad3 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -1905,7 +1905,7 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `type` | _[HTTPPathModifierType](#httppathmodifiertype)_ |  true  |  |
-| `replaceRegexMatch` | _[ReplaceRegexMatch](#replaceregexmatch)_ |  true  | ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite<br />Some examples:<br />(1) replaceRegexMatch:<br />      pattern: ^/service/([^/]+)(/.*)$<br />      substitution: \2/instance/\1<br />    Would transform /service/foo/v1/api into /v1/api/instance/foo.<br />(2) replaceRegexMatch:<br />      pattern: one<br />      substitution: two<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz.<br />(3) replaceRegexMatch:<br />      pattern: ^(.*?)one(.*)$<br />      substitution: \1two\2<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz.<br />(3) replaceRegexMatch:<br />      pattern: (?i)/xxx/<br />      substitution: /yyy/<br />    Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). |
+| `replaceRegexMatch` | _[ReplaceRegexMatch](#replaceregexmatch)_ |  false  | ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution.<br />https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite<br />Some examples:<br />(1) replaceRegexMatch:<br />      pattern: ^/service/([^/]+)(/.*)$<br />      substitution: \2/instance/\1<br />    Would transform /service/foo/v1/api into /v1/api/instance/foo.<br />(2) replaceRegexMatch:<br />      pattern: one<br />      substitution: two<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz.<br />(3) replaceRegexMatch:<br />      pattern: ^(.*?)one(.*)$<br />      substitution: \1two\2<br />    Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz.<br />(3) replaceRegexMatch:<br />      pattern: (?i)/xxx/<br />      substitution: /yyy/<br />    Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). |
 
 
 #### HTTPPathModifierType
diff --git a/test/cel-validation/httproutefilter_test.go b/test/cel-validation/httproutefilter_test.go
new file mode 100644
index 000000000..e9ebf380f
--- /dev/null
+++ b/test/cel-validation/httproutefilter_test.go
@@ -0,0 +1,120 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build celvalidation
+// +build celvalidation
+
+package celvalidation
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+)
+
+func TestHTTPRouteFilter(t *testing.T) {
+	ctx := context.Background()
+	baseHTTPRouteFilter := egv1a1.HTTPRouteFilter{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "hrf",
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: egv1a1.HTTPRouteFilterSpec{},
+	}
+
+	cases := []struct {
+		desc         string
+		mutate       func(httproutefilter *egv1a1.HTTPRouteFilter)
+		mutateStatus func(httproutefilter *egv1a1.HTTPRouteFilter)
+		wantErrors   []string
+	}{
+		{
+			desc: "Valid RegexHTTPPathModifier",
+			mutate: func(httproutefilter *egv1a1.HTTPRouteFilter) {
+				httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{
+					URLRewrite: &egv1a1.HTTPURLRewriteFilter{
+						Path: &egv1a1.HTTPPathModifier{
+							Type: egv1a1.RegexHTTPPathModifier,
+							ReplaceRegexMatch: &egv1a1.ReplaceRegexMatch{
+								Pattern:      "foo",
+								Substitution: "bar",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "invalid RegexHTTPPathModifier missing settings",
+			mutate: func(httproutefilter *egv1a1.HTTPRouteFilter) {
+				httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{
+					URLRewrite: &egv1a1.HTTPURLRewriteFilter{
+						Path: &egv1a1.HTTPPathModifier{
+							Type: egv1a1.RegexHTTPPathModifier,
+						},
+					},
+				}
+			},
+			wantErrors: []string{"spec.urlRewrite.path: Invalid value: \"object\": If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch field needs to be set."},
+		},
+		{
+			desc: "invalid RegexHTTPPathModifier missing pattern and substitution",
+			mutate: func(httproutefilter *egv1a1.HTTPRouteFilter) {
+				httproutefilter.Spec = egv1a1.HTTPRouteFilterSpec{
+					URLRewrite: &egv1a1.HTTPURLRewriteFilter{
+						Path: &egv1a1.HTTPPathModifier{
+							Type: egv1a1.RegexHTTPPathModifier,
+							ReplaceRegexMatch: &egv1a1.ReplaceRegexMatch{
+								Pattern:      "",
+								Substitution: "",
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.urlRewrite.path.replaceRegexMatch.pattern: Invalid value: \"\": spec.urlRewrite.path.replaceRegexMatch.pattern in body should be at least 1 chars long",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.desc, func(t *testing.T) {
+			httpRouteFilter := baseHTTPRouteFilter.DeepCopy()
+			httpRouteFilter.Name = fmt.Sprintf("hrf-%v", time.Now().UnixNano())
+
+			if tc.mutate != nil {
+				tc.mutate(httpRouteFilter)
+			}
+			err := c.Create(ctx, httpRouteFilter)
+
+			if tc.mutateStatus != nil {
+				tc.mutateStatus(httpRouteFilter)
+				err = c.Status().Update(ctx, httpRouteFilter)
+			}
+
+			if (len(tc.wantErrors) != 0) != (err != nil) {
+				t.Fatalf("Unexpected response while creating HTTPRouteFilter; got err=\n%v\n;want error=%v", err, tc.wantErrors)
+			}
+
+			var missingErrorStrings []string
+			for _, wantError := range tc.wantErrors {
+				if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) {
+					missingErrorStrings = append(missingErrorStrings, wantError)
+				}
+			}
+			if len(missingErrorStrings) != 0 {
+				t.Errorf("Unexpected response while creating HTTPRouteFilter; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings)
+			}
+		})
+	}
+}
diff --git a/test/e2e/testdata/httproute-rewrite-regex-path.yaml b/test/e2e/testdata/httproute-rewrite-regex-path.yaml
new file mode 100644
index 000000000..821550d73
--- /dev/null
+++ b/test/e2e/testdata/httproute-rewrite-regex-path.yaml
@@ -0,0 +1,35 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: rewrite-regex-path
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    filters:
+    - type: ExtensionRef
+      extensionRef:
+        group: gateway.envoyproxy.io
+        kind: HTTPRouteFilter
+        name: regex-path-rewrite
+    backendRefs:
+    - name: infra-backend-v1
+      port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: HTTPRouteFilter
+metadata:
+  name: regex-path-rewrite
+  namespace: gateway-conformance-infra
+spec:
+  urlRewrite:
+    path:
+      type: ReplaceRegexMatch
+      replaceRegexMatch:
+        pattern: '^/service/([^/]+)(/.*)$'
+        substitution: '\2/instance/\1'
diff --git a/test/e2e/tests/httproute_rewrite_regex_path.go b/test/e2e/tests/httproute_rewrite_regex_path.go
new file mode 100644
index 000000000..975d2ec88
--- /dev/null
+++ b/test/e2e/tests/httproute_rewrite_regex_path.go
@@ -0,0 +1,59 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build e2e
+// +build e2e
+
+package tests
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, HTTPRouteRewriteRegexPath)
+}
+
+var HTTPRouteRewriteRegexPath = suite.ConformanceTest{
+	ShortName:   "HTTPRouteRewriteRegexPath",
+	Description: "An HTTPRoute with path rewrite filter to replace a regex match",
+	Manifests:   []string{"testdata/httproute-rewrite-regex-path.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "rewrite-regex-path", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+		kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN)
+
+		testCases := []http.ExpectedResponse{
+			{
+				Request: http.Request{
+					Path: "/service/foo/v1/api",
+				},
+				ExpectedRequest: &http.ExpectedRequest{
+					Request: http.Request{
+						Path: "/v1/api/instance/foo",
+					},
+				},
+				Backend:   "infra-backend-v1",
+				Namespace: ns,
+			},
+		}
+		for i := range testCases {
+			// Declare tc here to avoid loop variable
+			// reuse issues across parallel tests.
+			tc := testCases[i]
+			t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+				t.Parallel()
+				http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
+			})
+		}
+	},
+}
diff --git a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
index 0bd873a34..c280e54ca 100644
--- a/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
+++ b/test/helm/gateway-helm/certjen-custom-scheduling.out.yaml
@@ -127,6 +127,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
index cd7ff1a53..3db52f2bc 100644
--- a/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
+++ b/test/helm/gateway-helm/control-plane-with-pdb.out.yaml
@@ -142,6 +142,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/default-config.out.yaml b/test/helm/gateway-helm/default-config.out.yaml
index 147c0f8ba..0dd66b5c2 100644
--- a/test/helm/gateway-helm/default-config.out.yaml
+++ b/test/helm/gateway-helm/default-config.out.yaml
@@ -127,6 +127,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/deployment-custom-topology.out.yaml b/test/helm/gateway-helm/deployment-custom-topology.out.yaml
index 7fdfef5e5..b9fc66251 100644
--- a/test/helm/gateway-helm/deployment-custom-topology.out.yaml
+++ b/test/helm/gateway-helm/deployment-custom-topology.out.yaml
@@ -127,6 +127,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/deployment-images-config.out.yaml b/test/helm/gateway-helm/deployment-images-config.out.yaml
index 5a2df408f..4da6c9a57 100644
--- a/test/helm/gateway-helm/deployment-images-config.out.yaml
+++ b/test/helm/gateway-helm/deployment-images-config.out.yaml
@@ -127,6 +127,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/envoy-gateway-config.out.yaml b/test/helm/gateway-helm/envoy-gateway-config.out.yaml
index 233334d59..056a0bde7 100644
--- a/test/helm/gateway-helm/envoy-gateway-config.out.yaml
+++ b/test/helm/gateway-helm/envoy-gateway-config.out.yaml
@@ -129,6 +129,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list
diff --git a/test/helm/gateway-helm/global-images-config.out.yaml b/test/helm/gateway-helm/global-images-config.out.yaml
index 95b98165d..5eea20550 100644
--- a/test/helm/gateway-helm/global-images-config.out.yaml
+++ b/test/helm/gateway-helm/global-images-config.out.yaml
@@ -131,6 +131,7 @@ rules:
   - securitypolicies
   - envoyextensionpolicies
   - backends
+  - httproutefilters
   verbs:
   - get
   - list