Repository: envoyproxy/gateway
Pull request #4487 (2024-10-21 04:33:06)

build(deps): bump github.com/replicatedhq/troubleshoot from 0.107.0 to 0.107.1
Bumps [github.com/replicatedhq/troubleshoot](https://github.com/replicatedhq/troubleshoot) from 0.107.0 to 0.107.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/replicatedhq/troubleshoot/releases">github.com/replicatedhq/troubleshoot's releases</a>.</em></p>
<blockquote>
<h2>v0.107.1</h2>
<h2>Changelog</h2>
<ul>
<li>289102f bug: fix nil check in host collector filter (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1653">#1653</a>)</li>
<li>77fd7da fix(make): make sure github action can check schemas change (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1651">#1651</a>)</li>
<li>78ee833 chore(deps): bump helm.sh/helm/v3 from 3.16.1 to 3.16.2 in /examples/sdk/helm-template in the security group (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1650">#1650</a>)</li>
<li>7818532 chore(deps): bump the security group with 3 updates (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1649">#1649</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/replicatedhq/troubleshoot/commit/289102f16ddfab5df50ba3c93145b6e4e93269ee"><code>289102f</code></a> bug: fix nil check in host collector filter (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1653">#1653</a>)</li>
<li><a href="https://github.com/replicatedhq/troubleshoot/commit/77fd7da315f886dd065ae9c1e4451075e1664850"><code>77fd7da</code></a> fix(make): make sure github action can check schemas change (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1651">#1651</a>)</li>
<li><a href="https://github.com/replicatedhq/troubleshoot/commit/78ee833f459b529e90c1b2410b6159ec1ce9797d"><code>78ee833</code></a> chore(deps): bump helm.sh/helm/v3 from 3.16.1 to 3.16.2 in /examples/sdk/helm...</li>
<li><a href="https://github.com/replicatedhq/troubleshoot/commit/781853251d9ac9f15deddbf3b2a4c5275a4a12e9"><code>7818532</code></a> chore(deps): bump the security group with 3 updates (<a href="https://redirect.github.com/replicatedhq/troubleshoot/issues/1649">#1649</a>)</li>
<li>See full diff in <a href="https://github.com/replicatedhq/troubleshoot/compare/v0.107.0...v0.107.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/replicatedhq/troubleshoot&package-manager=go_modules&previous-version=0.107.0&new-version=0.107.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/4487?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.82%. Comparing base [(`26ec49a`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/26ec49ab8ee3a03c90cd705cc9e90f8441cd97f2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`d0c1649`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/d0c1649754a838faea5e1a092f7014676774c856?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    #4487      +/-   ##
==========================================
- Coverage   65.83%   65.82%   -0.01%     
==========================================
  Files         203      203              
  Lines       30699    30699              
==========================================
- Hits        20212    20209       -3     
- Misses       9335     9337       +2     
- Partials     1152     1153       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/4487?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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8dc586ce1..c50e9d08e 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
 **What type of PR is this?**
 <!--
-Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary 
+Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary
 and summary followed by a colon. format `chore/docs/api/feat/fix/refactor/style/test: summary`.
 Examples:
 * "docs: fix grammar error"
@@ -23,3 +23,9 @@ a new PR, and we will review the API part first. It will save you lots of implem
 Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
 -->
 Fixes #
+
+<!--
+For any non-trivial changes, you need to provide a brief description of the changes in the release notes.
+Please add the description to the release-notes/current.yaml file and include this file in the PR.
+-->
+Release Notes: Yes/No
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 0f4771a3f..5fceea678 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -36,14 +36,14 @@ jobs:
     - uses: ./tools/github-actions/setup-deps
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b  # v3.26.12
+      uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b  # v3.26.13
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b  # v3.26.12
+      uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b  # v3.26.13
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b  # v3.26.12
+      uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b  # v3.26.13
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 987f7c2b6..018bb5c0d 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -40,6 +40,6 @@ jobs:
         retention-days: 5
 
     - name: "Upload to code-scanning"
-      uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b  # v3.26.12
+      uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b  # v3.26.13
       with:
         sarif_file: results.sarif
diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml
index 9e4a1460f..f34bd237a 100644
--- a/.github/workflows/trivy.yml
+++ b/.github/workflows/trivy.yml
@@ -25,7 +25,7 @@ jobs:
         IMAGE=envoy-proxy/gateway-dev TAG=${{ github.sha }} make image
 
     - name: Run Trivy vulnerability scanner
-      uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564  # v0.27.0
+      uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2  # v0.28.0
       with:
         image-ref: envoy-proxy/gateway-dev:${{ github.sha }}
         exit-code: '1'
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index 4e6118e70..4183c1283 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -74,7 +74,6 @@ type BackendTrafficPolicySpec struct {
 	// If multiple configurations are specified, the first one to match wins.
 	//
 	// +optional
-	// +notImplementedHide
 	ResponseOverride []*ResponseOverride `json:"responseOverride,omitempty"`
 }
 
diff --git a/api/v1alpha1/connection_types.go b/api/v1alpha1/connection_types.go
index 6f2779474..efb24dc3b 100644
--- a/api/v1alpha1/connection_types.go
+++ b/api/v1alpha1/connection_types.go
@@ -66,10 +66,9 @@ type BackendConnection struct {
 type ConnectionLimit struct {
 	// Value of the maximum concurrent connections limit.
 	// When the limit is reached, incoming connections will be closed after the CloseDelay duration.
-	// Default: unlimited.
 	//
-	// +kubebuilder:validation:Minimum=0
-	Value int64 `json:"value,omitempty"`
+	// +kubebuilder:validation:Minimum=1
+	Value int64 `json:"value"`
 
 	// CloseDelay defines the delay to use before closing connections that are rejected
 	// once the limit value is reached.
diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go
index d7a2a73ab..4bf7920f6 100644
--- a/api/v1alpha1/envoyproxy_types.go
+++ b/api/v1alpha1/envoyproxy_types.go
@@ -124,6 +124,8 @@ type EnvoyProxySpec struct {
 	//
 	// - envoy.filters.http.ratelimit
 	//
+	// - envoy.filters.http.custom_response
+	//
 	// - envoy.filters.http.router
 	//
 	// Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain.
@@ -174,7 +176,7 @@ type FilterPosition struct {
 }
 
 // EnvoyFilter defines the type of Envoy HTTP filter.
-// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.stateful_session;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit
+// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.stateful_session;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit;envoy.filters.http.custom_response
 type EnvoyFilter string
 
 const (
@@ -217,6 +219,9 @@ const (
 	// EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.
 	EnvoyFilterRateLimit EnvoyFilter = "envoy.filters.http.ratelimit"
 
+	// EnvoyFilterCustomResponse defines the Envoy HTTP custom response filter.
+	EnvoyFilterCustomResponse EnvoyFilter = "envoy.filters.http.custom_response"
+
 	// EnvoyFilterRouter defines the Envoy HTTP router filter.
 	EnvoyFilterRouter EnvoyFilter = "envoy.filters.http.router"
 )
diff --git a/api/v1alpha1/oidc_types.go b/api/v1alpha1/oidc_types.go
index dcc036157..dfe7a4604 100644
--- a/api/v1alpha1/oidc_types.go
+++ b/api/v1alpha1/oidc_types.go
@@ -119,7 +119,6 @@ type OIDCProvider struct {
 	// Other settings for the connection to the OIDC Provider can be specified in the BackendSettings resource.
 	//
 	// +optional
-	// +notImplementedHide
 	BackendCluster `json:",inline"`
 
 	// The OIDC Provider's [issuer identifier](https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery).
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index fe795c833..617151e29 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -627,33 +627,48 @@ type ResponseOverride struct {
 // CustomResponseMatch defines the configuration for matching a user response to return a custom one.
 type CustomResponseMatch struct {
 	// Status code to match on. The match evaluates to true if any of the matches are successful.
-	StatusCode []StatusCodeMatch `json:"statusCode"`
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=50
+	StatusCodes []StatusCodeMatch `json:"statusCodes"`
 }
 
 // StatusCodeValueType defines the types of values for the status code match supported by Envoy Gateway.
 // +kubebuilder:validation:Enum=Value;Range
 type StatusCodeValueType string
 
+const (
+	// StatusCodeValueTypeValue defines the "Value" status code match type.
+	StatusCodeValueTypeValue StatusCodeValueType = "Value"
+
+	// StatusCodeValueTypeRange defines the "Range" status code match type.
+	StatusCodeValueTypeRange StatusCodeValueType = "Range"
+)
+
+// StatusCodeMatch defines the configuration for matching a status code.
+// +kubebuilder:validation:XValidation:message="value must be set for type Value",rule="(!has(self.type) || self.type == 'Value')? has(self.value) : true"
+// +kubebuilder:validation:XValidation:message="range must be set for type Range",rule="(has(self.type) && self.type == 'Range')? has(self.range) : true"
 type StatusCodeMatch struct {
 	// Type is the type of value.
+	// Valid values are Value and Range, default is Value.
 	//
 	// +kubebuilder:default=Value
+	// +kubebuilder:validation:Enum=Value;Range
 	// +unionDiscriminator
 	Type *StatusCodeValueType `json:"type"`
 
 	// Value contains the value of the status code.
 	//
 	// +optional
-	Value *string `json:"value,omitempty"`
-	// ValueRef contains the contents of the body
-	// specified as a local object reference.
-	// Only a reference to ConfigMap is supported.
+	Value *int `json:"value,omitempty"`
+
+	// Range contains the range of status codes.
 	//
 	// +optional
 	Range *StatusCodeRange `json:"range,omitempty"`
 }
 
 // StatusCodeRange defines the configuration for define a range of status codes.
+// +kubebuilder:validation:XValidation: message="end must be greater than start",rule="self.end > self.start"
 type StatusCodeRange struct {
 	// Start of the range, including the start value.
 	Start int `json:"start"`
@@ -669,19 +684,31 @@ type CustomResponse struct {
 	ContentType *string `json:"contentType,omitempty"`
 
 	// Body of the Custom Response
-	//
-	// +optional
-	Body *CustomResponseBody `json:"body,omitempty"`
+	Body CustomResponseBody `json:"body"`
 }
 
 // ResponseValueType defines the types of values for the response body supported by Envoy Gateway.
 // +kubebuilder:validation:Enum=Inline;ValueRef
 type ResponseValueType string
 
+const (
+	// ResponseValueTypeInline defines the "Inline" response body type.
+	ResponseValueTypeInline ResponseValueType = "Inline"
+
+	// ResponseValueTypeValueRef defines the "ValueRef" response body type.
+	ResponseValueTypeValueRef ResponseValueType = "ValueRef"
+)
+
 // CustomResponseBody
+// +kubebuilder:validation:XValidation:message="inline must be set for type Inline",rule="(!has(self.type) || self.type == 'Inline')? has(self.inline) : true"
+// +kubebuilder:validation:XValidation:message="valueRef must be set for type ValueRef",rule="(has(self.type) && self.type == 'ValueRef')? has(self.valueRef) : true"
+// +kubebuilder:validation:XValidation:message="only ConfigMap is supported for ValueRef",rule="has(self.valueRef) ? self.valueRef.kind == 'ConfigMap' : true"
 type CustomResponseBody struct {
 	// Type is the type of method to use to read the body value.
+	// Valid values are Inline and ValueRef, default is Inline.
 	//
+	// +kubebuilder:default=Inline
+	// +kubebuilder:validation:Enum=Inline;ValueRef
 	// +unionDiscriminator
 	Type *ResponseValueType `json:"type"`
 
@@ -689,10 +716,14 @@ type CustomResponseBody struct {
 	//
 	// +optional
 	Inline *string `json:"inline,omitempty"`
+
 	// ValueRef contains the contents of the body
 	// specified as a local object reference.
 	// Only a reference to ConfigMap is supported.
 	//
+	// The value of key `response.body` in the ConfigMap will be used as the response body.
+	// If the key is not found, the first value in the ConfigMap will be used.
+	//
 	// +optional
 	ValueRef *gwapiv1.LocalObjectReference `json:"valueRef,omitempty"`
 }
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 696c99259..c225d65d3 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -1148,11 +1148,7 @@ func (in *CustomResponse) DeepCopyInto(out *CustomResponse) {
 		*out = new(string)
 		**out = **in
 	}
-	if in.Body != nil {
-		in, out := &in.Body, &out.Body
-		*out = new(CustomResponseBody)
-		(*in).DeepCopyInto(*out)
-	}
+	in.Body.DeepCopyInto(&out.Body)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponse.
@@ -1198,8 +1194,8 @@ func (in *CustomResponseBody) DeepCopy() *CustomResponseBody {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CustomResponseMatch) DeepCopyInto(out *CustomResponseMatch) {
 	*out = *in
-	if in.StatusCode != nil {
-		in, out := &in.StatusCode, &out.StatusCode
+	if in.StatusCodes != nil {
+		in, out := &in.StatusCodes, &out.StatusCodes
 		*out = make([]StatusCodeMatch, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
@@ -5166,7 +5162,7 @@ func (in *StatusCodeMatch) DeepCopyInto(out *StatusCodeMatch) {
 	}
 	if in.Value != nil {
 		in, out := &in.Value, &out.Value
-		*out = new(string)
+		*out = new(int)
 		**out = **in
 	}
 	if in.Range != nil {
diff --git a/charts/gateway-addons-helm/Chart.lock b/charts/gateway-addons-helm/Chart.lock
index 4b6f92ac7..228a952fd 100644
--- a/charts/gateway-addons-helm/Chart.lock
+++ b/charts/gateway-addons-helm/Chart.lock
@@ -16,6 +16,6 @@ dependencies:
   version: 1.3.1
 - name: opentelemetry-collector
   repository: https://open-telemetry.github.io/opentelemetry-helm-charts
-  version: 0.73.1
-digest: sha256:4c16df8d7efc27aff566fa5dfd2eba6527adbf3fc8e94e7e3ccfc0cee7836f1c
-generated: "2024-06-20T11:46:59.148579+08:00"
+  version: 0.108.0
+digest: sha256:ea6663bb1358123b96b69d2c5b0b8c20650a43dc39b24c482f0560201fd2cc3a
+generated: "2024-10-19T12:59:47.251089661+02:00"
diff --git a/charts/gateway-addons-helm/Chart.yaml b/charts/gateway-addons-helm/Chart.yaml
index 84ac6228f..2571ccec5 100644
--- a/charts/gateway-addons-helm/Chart.yaml
+++ b/charts/gateway-addons-helm/Chart.yaml
@@ -47,5 +47,5 @@ dependencies:
     condition: tempo.enabled
   - name: opentelemetry-collector
     repository: https://open-telemetry.github.io/opentelemetry-helm-charts
-    version: 0.73.1
+    version: 0.108.0
     condition: opentelemetry-collector.enabled
diff --git a/charts/gateway-addons-helm/README.md b/charts/gateway-addons-helm/README.md
index ccbd26b98..a52af3e2d 100644
--- a/charts/gateway-addons-helm/README.md
+++ b/charts/gateway-addons-helm/README.md
@@ -25,7 +25,7 @@ An Add-ons Helm chart for Envoy Gateway
 | https://grafana.github.io/helm-charts | grafana | 8.0.0 |
 | https://grafana.github.io/helm-charts | loki | 4.8.0 |
 | https://grafana.github.io/helm-charts | tempo | 1.3.1 |
-| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.73.1 |
+| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.108.0 |
 | https://prometheus-community.github.io/helm-charts | prometheus | 25.21.0 |
 
 ## Usage
@@ -103,7 +103,7 @@ To uninstall the chart:
 | loki.singleBinary.replicas | int | `1` |  |
 | loki.test.enabled | bool | `false` |  |
 | loki.write.replicas | int | `0` |  |
-| opentelemetry-collector.config.exporters.logging.verbosity | string | `"detailed"` |  |
+| opentelemetry-collector.config.exporters.debug.verbosity | string | `"detailed"` |  |
 | opentelemetry-collector.config.exporters.loki.endpoint | string | `"http://loki.monitoring.svc:3100/loki/api/v1/push"` |  |
 | opentelemetry-collector.config.exporters.otlp.endpoint | string | `"tempo.monitoring.svc:4317"` |  |
 | opentelemetry-collector.config.exporters.otlp.tls.insecure | bool | `true` |  |
@@ -112,6 +112,7 @@ To uninstall the chart:
 | opentelemetry-collector.config.processors.attributes.actions[0].action | string | `"insert"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].key | string | `"loki.attribute.labels"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].value | string | `"k8s.pod.name, k8s.namespace.name"` |  |
+| opentelemetry-collector.config.receivers.datadog.endpoint | string | `"${env:MY_POD_IP}:8126"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.grpc.endpoint | string | `"${env:MY_POD_IP}:4317"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.http.endpoint | string | `"${env:MY_POD_IP}:4318"` |  |
 | opentelemetry-collector.config.receivers.zipkin.endpoint | string | `"${env:MY_POD_IP}:9411"` |  |
@@ -120,12 +121,15 @@ To uninstall the chart:
 | opentelemetry-collector.config.service.pipelines.logs.processors[0] | string | `"attributes"` |  |
 | opentelemetry-collector.config.service.pipelines.logs.receivers[0] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.metrics.exporters[0] | string | `"prometheus"` |  |
-| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[1] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.traces.exporters[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"zipkin"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[2] | string | `"zipkin"` |  |
 | opentelemetry-collector.enabled | bool | `false` |  |
 | opentelemetry-collector.fullnameOverride | string | `"otel-collector"` |  |
+| opentelemetry-collector.image.repository | string | `"otel/opentelemetry-collector-contrib"` |  |
 | opentelemetry-collector.mode | string | `"deployment"` |  |
 | prometheus.alertmanager.enabled | bool | `false` |  |
 | prometheus.enabled | bool | `true` |  |
diff --git a/charts/gateway-addons-helm/values.yaml b/charts/gateway-addons-helm/values.yaml
index 55a02b682..d3fb043dd 100644
--- a/charts/gateway-addons-helm/values.yaml
+++ b/charts/gateway-addons-helm/values.yaml
@@ -181,11 +181,13 @@ opentelemetry-collector:
   enabled: false
   fullnameOverride: otel-collector
   mode: deployment
+  image:
+    repository: "otel/opentelemetry-collector-contrib"
   config:
     exporters:
       prometheus:
         endpoint: 0.0.0.0:19001
-      logging:
+      debug:
         verbosity: detailed
       loki:
         endpoint: "http://loki.monitoring.svc:3100/loki/api/v1/push"
@@ -207,6 +209,8 @@ opentelemetry-collector:
             # Loki will convert this to k8s_pod_name label.
             value: k8s.pod.name, k8s.namespace.name
     receivers:
+      datadog:
+        endpoint: ${env:MY_POD_IP}:8126
       zipkin:
         endpoint: ${env:MY_POD_IP}:9411
       otlp:
@@ -223,6 +227,7 @@ opentelemetry-collector:
           exporters:
             - prometheus
           receivers:
+            - datadog
             - otlp
         logs:
           exporters:
@@ -235,5 +240,6 @@ opentelemetry-collector:
           exporters:
             - otlp
           receivers:
+            - datadog
             - otlp
             - zipkin
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 7b2e93731..f9fb0f329 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -940,16 +940,15 @@ spec:
                     match:
                       description: Match configuration.
                       properties:
-                        statusCode:
+                        statusCodes:
                           description: Status code to match on. The match evaluates
                             to true if any of the matches are successful.
                           items:
+                            description: StatusCodeMatch defines the configuration
+                              for matching a status code.
                             properties:
                               range:
-                                description: |-
-                                  ValueRef contains the contents of the body
-                                  specified as a local object reference.
-                                  Only a reference to ConfigMap is supported.
+                                description: Range contains the range of status codes.
                                 properties:
                                   end:
                                     description: End of the range, including the end
@@ -963,23 +962,41 @@ spec:
                                 - end
                                 - start
                                 type: object
+                                x-kubernetes-validations:
+                                - message: end must be greater than start
+                                  rule: self.end > self.start
                               type:
+                                allOf:
+                                - enum:
+                                  - Value
+                                  - Range
+                                - enum:
+                                  - Value
+                                  - Range
                                 default: Value
-                                description: Type is the type of value.
-                                enum:
-                                - Value
-                                - Range
+                                description: |-
+                                  Type is the type of value.
+                                  Valid values are Value and Range, default is Value.
                                 type: string
                               value:
                                 description: Value contains the value of the status
                                   code.
-                                type: string
+                                type: integer
                             required:
                             - type
                             type: object
+                            x-kubernetes-validations:
+                            - message: value must be set for type Value
+                              rule: '(!has(self.type) || self.type == ''Value'')?
+                                has(self.value) : true'
+                            - message: range must be set for type Range
+                              rule: '(has(self.type) && self.type == ''Range'')? has(self.range)
+                                : true'
+                          maxItems: 50
+                          minItems: 1
                           type: array
                       required:
-                      - statusCode
+                      - statusCodes
                       type: object
                     response:
                       description: Response configuration.
@@ -992,17 +1009,26 @@ spec:
                                 string.
                               type: string
                             type:
-                              description: Type is the type of method to use to read
-                                the body value.
-                              enum:
-                              - Inline
-                              - ValueRef
+                              allOf:
+                              - enum:
+                                - Inline
+                                - ValueRef
+                              - enum:
+                                - Inline
+                                - ValueRef
+                              default: Inline
+                              description: |-
+                                Type is the type of method to use to read the body value.
+                                Valid values are Inline and ValueRef, default is Inline.
                               type: string
                             valueRef:
                               description: |-
                                 ValueRef contains the contents of the body
                                 specified as a local object reference.
                                 Only a reference to ConfigMap is supported.
+
+                                The value of key `response.body` in the ConfigMap will be used as the response body.
+                                If the key is not found, the first value in the ConfigMap will be used.
                               properties:
                                 group:
                                   description: |-
@@ -1031,10 +1057,22 @@ spec:
                           required:
                           - type
                           type: object
+                          x-kubernetes-validations:
+                          - message: inline must be set for type Inline
+                            rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline)
+                              : true'
+                          - message: valueRef must be set for type ValueRef
+                            rule: '(has(self.type) && self.type == ''ValueRef'')?
+                              has(self.valueRef) : true'
+                          - message: only ConfigMap is supported for ValueRef
+                            rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap''
+                              : true'
                         contentType:
                           description: Content Type of the response. This will be
                             set in the Content-Type header.
                           type: string
+                      required:
+                      - body
                       type: object
                   required:
                   - match
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
index 582486e70..3e626f3f8 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -125,10 +125,11 @@ spec:
                         description: |-
                           Value of the maximum concurrent connections limit.
                           When the limit is reached, incoming connections will be closed after the CloseDelay duration.
-                          Default: unlimited.
                         format: int64
-                        minimum: 0
+                        minimum: 1
                         type: integer
+                    required:
+                    - value
                     type: object
                   socketBufferLimit:
                     allOf:
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
index 5b0130f27..0733ed112 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml
@@ -305,6 +305,8 @@ spec:
 
                   - envoy.filters.http.ratelimit
 
+                  - envoy.filters.http.custom_response
+
                   - envoy.filters.http.router
 
                   Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain.
@@ -330,6 +332,7 @@ spec:
                       - envoy.filters.http.rbac
                       - envoy.filters.http.local_ratelimit
                       - envoy.filters.http.ratelimit
+                      - envoy.filters.http.custom_response
                       type: string
                     before:
                       description: |-
@@ -349,6 +352,7 @@ spec:
                       - envoy.filters.http.rbac
                       - envoy.filters.http.local_ratelimit
                       - envoy.filters.http.ratelimit
+                      - envoy.filters.http.custom_response
                       type: string
                     name:
                       description: Name of the filter.
@@ -366,6 +370,7 @@ spec:
                       - envoy.filters.http.rbac
                       - envoy.filters.http.local_ratelimit
                       - envoy.filters.http.ratelimit
+                      - envoy.filters.http.custom_response
                       type: string
                   required:
                   - name
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 8a75fec42..672cfb59d 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_httproutefilters.yaml
@@ -60,17 +60,26 @@ spec:
                         description: Inline contains the value as an inline string.
                         type: string
                       type:
-                        description: Type is the type of method to use to read the
-                          body value.
-                        enum:
-                        - Inline
-                        - ValueRef
+                        allOf:
+                        - enum:
+                          - Inline
+                          - ValueRef
+                        - enum:
+                          - Inline
+                          - ValueRef
+                        default: Inline
+                        description: |-
+                          Type is the type of method to use to read the body value.
+                          Valid values are Inline and ValueRef, default is Inline.
                         type: string
                       valueRef:
                         description: |-
                           ValueRef contains the contents of the body
                           specified as a local object reference.
                           Only a reference to ConfigMap is supported.
+
+                          The value of key `response.body` in the ConfigMap will be used as the response body.
+                          If the key is not found, the first value in the ConfigMap will be used.
                         properties:
                           group:
                             description: |-
@@ -99,6 +108,16 @@ spec:
                     required:
                     - type
                     type: object
+                    x-kubernetes-validations:
+                    - message: inline must be set for type Inline
+                      rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline)
+                        : true'
+                    - message: valueRef must be set for type ValueRef
+                      rule: '(has(self.type) && self.type == ''ValueRef'')? has(self.valueRef)
+                        : true'
+                    - message: only ConfigMap is supported for ValueRef
+                      rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap''
+                        : true'
                   contentType:
                     description: Content Type of the response. This will be set in
                       the Content-Type header.
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index 92af04381..25eb15516 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -5,7 +5,7 @@ go 1.23.1
 require (
 	github.com/envoyproxy/gateway v1.0.2
 	github.com/envoyproxy/go-control-plane v0.13.1
-	github.com/urfave/cli/v2 v2.27.4
+	github.com/urfave/cli/v2 v2.27.5
 	google.golang.org/grpc v1.67.1
 	google.golang.org/protobuf v1.35.1
 	k8s.io/apimachinery v0.31.1
@@ -17,7 +17,7 @@ require (
 	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-20240723142845-024c85f92f20 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
 	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index 1df719e00..29bfba9e9 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -4,8 +4,8 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr
 github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
 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/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
+github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -64,8 +64,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
-github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
+github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
+github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
diff --git a/go.mod b/go.mod
index 0b7c29eb5..4d03c6822 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/miekg/dns v1.1.62
 	github.com/ohler55/ojg v1.24.1
-	github.com/prometheus/client_golang v1.20.4
+	github.com/prometheus/client_golang v1.20.5
 	github.com/prometheus/common v0.60.0
 	github.com/spf13/cobra v1.8.1
 	github.com/spf13/pflag v1.0.5
@@ -63,7 +63,7 @@ require (
 
 require (
 	github.com/docker/docker v27.3.1+incompatible
-	github.com/replicatedhq/troubleshoot v0.107.0
+	github.com/replicatedhq/troubleshoot v0.107.1
 	google.golang.org/grpc v1.67.1
 	sigs.k8s.io/kubectl-validate v0.0.5-0.20240827210056-ce13d95db263
 )
@@ -281,7 +281,7 @@ require (
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.24.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 5aa804824..d11cb03bb 100644
--- a/go.sum
+++ b/go.sum
@@ -689,8 +689,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.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
-github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
+github.com/prometheus/client_golang v1.20.5/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=
@@ -717,8 +717,8 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb
 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
 github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
 github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
-github.com/replicatedhq/troubleshoot v0.107.0 h1:AzaKBxNKuIQpERyitjJ4EINZTz4vlUacW5QguZZkTww=
-github.com/replicatedhq/troubleshoot v0.107.0/go.mod h1:AjaLpi9kidsFAuyAvBrvcG04w49WIDm7Iy6XyMVMv6U=
+github.com/replicatedhq/troubleshoot v0.107.1 h1:Hx9VbVv1r3M5fiH2fPTeoZ8LNIxh5R/e6vpe2jBgPfc=
+github.com/replicatedhq/troubleshoot v0.107.1/go.mod h1:6mZzcO/EWVBNXVnFdSHfPaoTnjcQdV3sq61NkBF60YE=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index b8f289a9d..89b6804a2 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -10,9 +10,11 @@ import (
 	"fmt"
 	"math"
 	"sort"
+	"strconv"
 	"strings"
 
 	perr "github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -34,6 +36,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv
 	gateways []*GatewayContext,
 	routes []RouteContext,
 	xdsIR resource.XdsIRMap,
+	configMaps []*corev1.ConfigMap,
 ) []*egv1a1.BackendTrafficPolicy {
 	res := []*egv1a1.BackendTrafficPolicy{}
 
@@ -127,7 +130,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv
 				}
 
 				// Set conditions for translation error if it got any
-				if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR); err != nil {
+				if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, configMaps); err != nil {
 					status.SetTranslationErrorForPolicyAncestors(&policy.Status,
 						ancestorRefs,
 						t.GatewayControllerName,
@@ -181,7 +184,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv
 				}
 
 				// Set conditions for translation error if it got any
-				if err := t.translateBackendTrafficPolicyForGateway(policy, currTarget, gateway, xdsIR); err != nil {
+				if err := t.translateBackendTrafficPolicyForGateway(policy, currTarget, gateway, xdsIR, configMaps); err != nil {
 					status.SetTranslationErrorForPolicyAncestors(&policy.Status,
 						ancestorRefs,
 						t.GatewayControllerName,
@@ -281,7 +284,12 @@ func resolveBTPolicyRouteTargetRef(policy *egv1a1.BackendTrafficPolicy, target g
 	return route.RouteContext, nil
 }
 
-func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.BackendTrafficPolicy, route RouteContext, xdsIR resource.XdsIRMap) error {
+func (t *Translator) translateBackendTrafficPolicyForRoute(
+	policy *egv1a1.BackendTrafficPolicy,
+	route RouteContext,
+	xdsIR resource.XdsIRMap,
+	configMaps []*corev1.ConfigMap,
+) error {
 	var (
 		rl        *ir.RateLimit
 		lb        *ir.LoadBalancer
@@ -295,6 +303,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 		bc        *ir.BackendConnection
 		ds        *ir.DNS
 		h2        *ir.HTTP2Settings
+		ro        *ir.ResponseOverride
 		err, errs error
 	)
 
@@ -340,6 +349,11 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 		errs = errors.Join(errs, err)
 	}
 
+	if ro, err = buildResponseOverride(policy, configMaps); err != nil {
+		err = perr.WithMessage(err, "ResponseOverride")
+		errs = errors.Join(errs, err)
+	}
+
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
 	// Apply IR to all relevant routes
@@ -402,6 +416,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 						HTTP2:             h2,
 						DNS:               ds,
 						Timeout:           to,
+						ResponseOverride:  ro,
 					}
 
 					// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -418,7 +433,13 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 	return errs
 }
 
-func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.BackendTrafficPolicy, target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName, gateway *GatewayContext, xdsIR resource.XdsIRMap) error {
+func (t *Translator) translateBackendTrafficPolicyForGateway(
+	policy *egv1a1.BackendTrafficPolicy,
+	target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
+	gateway *GatewayContext,
+	xdsIR resource.XdsIRMap,
+	configMaps []*corev1.ConfigMap,
+) error {
 	var (
 		rl        *ir.RateLimit
 		lb        *ir.LoadBalancer
@@ -431,6 +452,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		rt        *ir.Retry
 		ds        *ir.DNS
 		h2        *ir.HTTP2Settings
+		ro        *ir.ResponseOverride
 		err, errs error
 	)
 
@@ -469,6 +491,10 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		err = perr.WithMessage(err, "HTTP2")
 		errs = errors.Join(errs, err)
 	}
+	if ro, err = buildResponseOverride(policy, configMaps); err != nil {
+		err = perr.WithMessage(err, "ResponseOverride")
+		errs = errors.Join(errs, err)
+	}
 
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
@@ -542,16 +568,17 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 			}
 
 			r.Traffic = &ir.TrafficFeatures{
-				RateLimit:      rl,
-				LoadBalancer:   lb,
-				ProxyProtocol:  pp,
-				HealthCheck:    hc,
-				CircuitBreaker: cb,
-				FaultInjection: fi,
-				TCPKeepalive:   ka,
-				Retry:          rt,
-				HTTP2:          h2,
-				DNS:            ds,
+				RateLimit:        rl,
+				LoadBalancer:     lb,
+				ProxyProtocol:    pp,
+				HealthCheck:      hc,
+				CircuitBreaker:   cb,
+				FaultInjection:   fi,
+				TCPKeepalive:     ka,
+				Retry:            rt,
+				HTTP2:            h2,
+				DNS:              ds,
+				ResponseOverride: ro,
 			}
 
 			// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -836,3 +863,81 @@ func makeIrTriggerSet(in []egv1a1.TriggerEnum) []ir.TriggerEnum {
 	}
 	return irTriggers
 }
+
+func buildResponseOverride(policy *egv1a1.BackendTrafficPolicy, configMaps []*corev1.ConfigMap) (*ir.ResponseOverride, error) {
+	if len(policy.Spec.ResponseOverride) == 0 {
+		return nil, nil
+	}
+
+	rules := make([]ir.ResponseOverrideRule, 0, len(policy.Spec.ResponseOverride))
+	for index, ro := range policy.Spec.ResponseOverride {
+		match := ir.CustomResponseMatch{
+			StatusCodes: make([]ir.StatusCodeMatch, 0, len(ro.Match.StatusCodes)),
+		}
+
+		for _, code := range ro.Match.StatusCodes {
+			if code.Type != nil && *code.Type == egv1a1.StatusCodeValueTypeRange {
+				match.StatusCodes = append(match.StatusCodes, ir.StatusCodeMatch{
+					Range: &ir.StatusCodeRange{
+						Start: code.Range.Start,
+						End:   code.Range.End,
+					},
+				})
+			} else {
+				match.StatusCodes = append(match.StatusCodes, ir.StatusCodeMatch{
+					Value: code.Value,
+				})
+			}
+		}
+
+		response := ir.CustomResponse{
+			ContentType: ro.Response.ContentType,
+		}
+
+		if ro.Response.Body.Type != nil && *ro.Response.Body.Type == egv1a1.ResponseValueTypeValueRef {
+			foundCM := false
+			for _, cm := range configMaps {
+				if cm.Namespace == policy.Namespace && cm.Name == string(ro.Response.Body.ValueRef.Name) {
+					body, dataOk := cm.Data["response.body"]
+					switch {
+					case dataOk:
+						response.Body = body
+					case len(cm.Data) > 0: // Fallback to the first key if response.body is not found
+						for _, value := range cm.Data {
+							body = value
+							break
+						}
+						response.Body = body
+					default:
+						return nil, fmt.Errorf("can't find the key response.body in the referenced configmap %s", ro.Response.Body.ValueRef.Name)
+					}
+
+					foundCM = true
+					break
+				}
+			}
+			if !foundCM {
+				return nil, fmt.Errorf("can't find the referenced configmap %s", ro.Response.Body.ValueRef.Name)
+			}
+		} else {
+			response.Body = *ro.Response.Body.Inline
+		}
+
+		rules = append(rules, ir.ResponseOverrideRule{
+			Name:     defaultResponseOverrideRuleName(policy, index),
+			Match:    match,
+			Response: response,
+		})
+	}
+	return &ir.ResponseOverride{
+		Name:  irConfigName(policy),
+		Rules: rules,
+	}, nil
+}
+
+func defaultResponseOverrideRuleName(policy *egv1a1.BackendTrafficPolicy, index int) string {
+	return fmt.Sprintf(
+		"%s/responseoverride/rule/%s",
+		irConfigName(policy),
+		strconv.Itoa(index))
+}
diff --git a/internal/gatewayapi/helpers_v1alpha2.go b/internal/gatewayapi/helpers_v1alpha2.go
deleted file mode 100644
index 3b1dffde6..000000000
--- a/internal/gatewayapi/helpers_v1alpha2.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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.
-
-// This file contains code derived from Contour,
-// https://github.com/projectcontour/contour
-// and is provided here subject to the following:
-// Copyright Project Contour Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package gatewayapi
-
-import (
-	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
-	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
-)
-
-// TODO: [gwapiv1a2-gwapiv1]
-// This file can be removed once all routes graduates to gwapiv1.
-
-// UpgradeBackendRef converts gwapiv1a2.BackendRef to gwapiv1.BackendRef
-func UpgradeBackendRef(old gwapiv1a2.BackendRef) gwapiv1.BackendRef {
-	upgraded := gwapiv1.BackendRef{}
-
-	if old.Group != nil {
-		upgraded.Group = GroupPtr(string(*old.Group))
-	}
-
-	if old.Kind != nil {
-		upgraded.Kind = KindPtr(string(*old.Kind))
-	}
-
-	if old.Namespace != nil {
-		upgraded.Namespace = NamespacePtr(string(*old.Namespace))
-	}
-
-	upgraded.Name = old.Name
-
-	if old.Port != nil {
-		upgraded.Port = PortNumPtr(int32(*old.Port))
-	}
-
-	return upgraded
-}
diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go
index 0e6d683d8..0f518b710 100644
--- a/internal/gatewayapi/translator.go
+++ b/internal/gatewayapi/translator.go
@@ -211,7 +211,7 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult,
 
 	// Process BackendTrafficPolicies
 	backendTrafficPolicies := t.ProcessBackendTrafficPolicies(
-		resources.BackendTrafficPolicies, gateways, routes, xdsIR)
+		resources.BackendTrafficPolicies, gateways, routes, xdsIR, resources.ConfigMaps)
 
 	// Process SecurityPolicies
 	securityPolicies := t.ProcessSecurityPolicies(
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index fdcace324..cb5021f4c 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -477,6 +477,63 @@ type HTTP2Settings struct {
 	ResetStreamOnError *bool `json:"resetStreamOnError,omitempty" yaml:"resetStreamOnError,omitempty"`
 }
 
+// ResponseOverride defines the configuration to override specific responses with a custom one.
+// +k8s:deepcopy-gen=true
+type ResponseOverride struct {
+	// Name is a unique name for a ResponseOverride configuration.
+	// The xds translator only generates one CustomResponse filter for each unique name.
+	Name string `json:"name" yaml:"name"`
+
+	// Rules contains the list of rules to override responses.
+	Rules []ResponseOverrideRule `json:"rules,omitempty"`
+}
+
+// ResponseOverrideRule defines the configuration for overriding a response.
+// +k8s:deepcopy-gen=true
+type ResponseOverrideRule struct {
+	// Name is a generated name for the rule.
+	Name string `json:"name"`
+	// Match configuration.
+	Match CustomResponseMatch `json:"match"`
+	// Response configuration.
+	Response CustomResponse `json:"response"`
+}
+
+// CustomResponseMatch defines the configuration for matching a user response to return a custom one.
+// +k8s:deepcopy-gen=true
+type CustomResponseMatch struct {
+	// Status code to match on. The match evaluates to true if any of the matches are successful.
+	StatusCodes []StatusCodeMatch `json:"statusCodes"`
+}
+
+// StatusCodeMatch defines the configuration for matching a status code.
+// +k8s:deepcopy-gen=true
+type StatusCodeMatch struct {
+	// Value contains the value of the status code.
+	Value *int `json:"value,omitempty"`
+
+	// Range contains a range of status codes.
+	Range *StatusCodeRange `json:"range,omitempty"`
+}
+
+// StatusCodeRange defines the configuration for define a range of status codes.
+type StatusCodeRange struct {
+	// Start of the range, including the start value.
+	Start int `json:"start"`
+	// End of the range, including the end value.
+	End int `json:"end"`
+}
+
+// CustomResponse defines the configuration for returning a custom response.
+// +k8s:deepcopy-gen=true
+type CustomResponse struct {
+	// Content Type of the response. This will be set in the Content-Type header.
+	ContentType *string `json:"contentType,omitempty"`
+
+	// Body of the Custom Response
+	Body string `json:"body"`
+}
+
 // HealthCheckSettings provides HealthCheck configuration on the HTTP/HTTPS listener.
 // +k8s:deepcopy-gen=true
 type HealthCheckSettings egv1a1.HealthCheckSettings
@@ -657,6 +714,8 @@ type TrafficFeatures struct {
 	HTTP2 *HTTP2Settings `json:"http2,omitempty" yaml:"http2,omitempty"`
 	// DNS is used to configure how DNS resolution is handled by the Envoy Proxy cluster
 	DNS *DNS `json:"dns,omitempty" yaml:"dns,omitempty"`
+	// ResponseOverride defines the schema for overriding the response.
+	ResponseOverride *ResponseOverride `json:"responseOverride,omitempty" yaml:"responseOverride,omitempty"`
 }
 
 func (b *TrafficFeatures) Validate() error {
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index 3c0c1135f..791b6d5dd 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -602,6 +602,48 @@ func (in *CoreListenerDetails) DeepCopy() *CoreListenerDetails {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomResponse) DeepCopyInto(out *CustomResponse) {
+	*out = *in
+	if in.ContentType != nil {
+		in, out := &in.ContentType, &out.ContentType
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponse.
+func (in *CustomResponse) DeepCopy() *CustomResponse {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomResponse)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomResponseMatch) DeepCopyInto(out *CustomResponseMatch) {
+	*out = *in
+	if in.StatusCodes != nil {
+		in, out := &in.StatusCodes, &out.StatusCodes
+		*out = make([]StatusCodeMatch, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResponseMatch.
+func (in *CustomResponseMatch) DeepCopy() *CustomResponseMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomResponseMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DNS) DeepCopyInto(out *DNS) {
 	*out = *in
@@ -2399,6 +2441,45 @@ func (in *ResourceMetadata) DeepCopy() *ResourceMetadata {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResponseOverride) DeepCopyInto(out *ResponseOverride) {
+	*out = *in
+	if in.Rules != nil {
+		in, out := &in.Rules, &out.Rules
+		*out = make([]ResponseOverrideRule, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseOverride.
+func (in *ResponseOverride) DeepCopy() *ResponseOverride {
+	if in == nil {
+		return nil
+	}
+	out := new(ResponseOverride)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResponseOverrideRule) DeepCopyInto(out *ResponseOverrideRule) {
+	*out = *in
+	in.Match.DeepCopyInto(&out.Match)
+	in.Response.DeepCopyInto(&out.Response)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseOverrideRule.
+func (in *ResponseOverrideRule) DeepCopy() *ResponseOverrideRule {
+	if in == nil {
+		return nil
+	}
+	out := new(ResponseOverrideRule)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Retry) DeepCopyInto(out *Retry) {
 	*out = *in
@@ -2590,6 +2671,31 @@ func (in *SlowStart) DeepCopy() *SlowStart {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StatusCodeMatch) DeepCopyInto(out *StatusCodeMatch) {
+	*out = *in
+	if in.Value != nil {
+		in, out := &in.Value, &out.Value
+		*out = new(int)
+		**out = **in
+	}
+	if in.Range != nil {
+		in, out := &in.Range, &out.Range
+		*out = new(StatusCodeRange)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusCodeMatch.
+func (in *StatusCodeMatch) DeepCopy() *StatusCodeMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(StatusCodeMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StringMatch) DeepCopyInto(out *StringMatch) {
 	*out = *in
@@ -3159,6 +3265,11 @@ func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
 		*out = new(DNS)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ResponseOverride != nil {
+		in, out := &in.ResponseOverride, &out.ResponseOverride
+		*out = new(ResponseOverride)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficFeatures.
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 7fe3c3d32..de020dfce 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -239,7 +239,7 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques
 		}
 
 		// Add all BackendTrafficPolicies to the resourceTree
-		if err = r.processBackendTrafficPolicies(ctx, gwcResource); err != nil {
+		if err = r.processBackendTrafficPolicies(ctx, gwcResource, resourceMappings); err != nil {
 			return reconcile.Result{}, err
 		}
 
@@ -748,6 +748,39 @@ func (r *gatewayAPIReconciler) processConfigMapRef(
 	return nil
 }
 
+// processBtpConfigMapRefs adds the referenced ConfigMaps in BackendTrafficPolicies
+// to the resourceTree
+func (r *gatewayAPIReconciler) processBtpConfigMapRefs(
+	ctx context.Context, resourceTree *resource.Resources, resourceMap *resourceMappings,
+) {
+	for _, policy := range resourceTree.BackendTrafficPolicies {
+		for _, ro := range policy.Spec.ResponseOverride {
+			if ro.Response.Body.ValueRef != nil && string(ro.Response.Body.ValueRef.Kind) == resource.KindConfigMap {
+				configMap := new(corev1.ConfigMap)
+				err := r.client.Get(ctx,
+					types.NamespacedName{Namespace: policy.Namespace, Name: string(ro.Response.Body.ValueRef.Name)},
+					configMap,
+				)
+				// we don't return an error here, because we want to continue
+				// reconciling the rest of the BackendTrafficPolicies despite that this
+				// reference is invalid.
+				// This BackendTrafficPolicies will be marked as invalid in its status
+				// when translating to IR because the referenced configmap can't be
+				// found.
+				if err != nil {
+					r.log.Error(err,
+						"failed to process ResponseOverride ValueRef for BackendTrafficPolicy",
+						"policy", policy, "ValueRef", ro.Response.Body.ValueRef.Name)
+				}
+
+				resourceMap.allAssociatedNamespaces.Insert(policy.Namespace)
+				resourceTree.ConfigMaps = append(resourceTree.ConfigMaps, configMap)
+				r.log.Info("processing ConfigMap", "namespace", policy.Namespace, "name", string(ro.Response.Body.ValueRef.Name))
+			}
+		}
+	}
+}
+
 func (r *gatewayAPIReconciler) getNamespace(ctx context.Context, name string) (*corev1.Namespace, error) {
 	nsKey := types.NamespacedName{Name: name}
 	ns := new(corev1.Namespace)
@@ -942,7 +975,8 @@ func (r *gatewayAPIReconciler) processClientTrafficPolicies(
 }
 
 // processBackendTrafficPolicies adds BackendTrafficPolicies to the resourceTree
-func (r *gatewayAPIReconciler) processBackendTrafficPolicies(ctx context.Context, resourceTree *resource.Resources) error {
+func (r *gatewayAPIReconciler) processBackendTrafficPolicies(ctx context.Context, resourceTree *resource.Resources, resourceMap *resourceMappings,
+) error {
 	backendTrafficPolicies := egv1a1.BackendTrafficPolicyList{}
 	if err := r.client.List(ctx, &backendTrafficPolicies); err != nil {
 		return fmt.Errorf("error listing BackendTrafficPolicies: %w", err)
@@ -955,6 +989,7 @@ func (r *gatewayAPIReconciler) processBackendTrafficPolicies(ctx context.Context
 		policy.Status = gwapiv1a2.PolicyStatus{}
 		resourceTree.BackendTrafficPolicies = append(resourceTree.BackendTrafficPolicies, &policy)
 	}
+	r.processBtpConfigMapRefs(ctx, resourceTree, resourceMap)
 	return nil
 }
 
@@ -1348,7 +1383,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
 		return err
 	}
 
-	// Watch ConfigMap CRUDs and process affected ClienTraffiPolicies and BackendTLSPolicies.
+	// Watch ConfigMap CRUDs and process affected EG Resources.
 	configMapPredicates := []predicate.TypedPredicate[*corev1.ConfigMap]{
 		predicate.NewTypedPredicateFuncs[*corev1.ConfigMap](func(cm *corev1.ConfigMap) bool {
 			return r.validateConfigMapForReconcile(cm)
@@ -1492,6 +1527,10 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
 		return err
 	}
 
+	if err := addBtpIndexers(ctx, mgr); err != nil {
+		return err
+	}
+
 	// Watch SecurityPolicy
 	spPredicates := []predicate.TypedPredicate[*egv1a1.SecurityPolicy]{
 		predicate.TypedGenerationChangedPredicate[*egv1a1.SecurityPolicy]{},
diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go
index 462a70542..2ad12069f 100644
--- a/internal/provider/kubernetes/indexers.go
+++ b/internal/provider/kubernetes/indexers.go
@@ -46,6 +46,7 @@ const (
 	secretEnvoyProxyIndex            = "secretEnvoyProxyIndex"
 	secretEnvoyExtensionPolicyIndex  = "secretEnvoyExtensionPolicyIndex"
 	httpRouteFilterHTTPRouteIndex    = "httpRouteFilterHTTPRouteIndex"
+	configMapBtpIndex                = "configMapBtpIndex"
 )
 
 func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error {
@@ -641,6 +642,36 @@ func secretCtpIndexFunc(rawObj client.Object) []string {
 	return secretReferences
 }
 
+// addBtpIndexers adds indexing on BackendTrafficPolicy, for ConfigMap objects that are
+// referenced in BackendTrafficPolicy objects. This helps in querying for BackendTrafficPolies that are
+// affected by a particular ConfigMap CRUD.
+func addBtpIndexers(ctx context.Context, mgr manager.Manager) error {
+	if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.BackendTrafficPolicy{}, configMapBtpIndex, configMapBtpIndexFunc); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func configMapBtpIndexFunc(rawObj client.Object) []string {
+	btp := rawObj.(*egv1a1.BackendTrafficPolicy)
+	var configMapReferences []string
+
+	for _, ro := range btp.Spec.ResponseOverride {
+		if ro.Response.Body.ValueRef != nil {
+			if string(ro.Response.Body.ValueRef.Kind) == resource.KindConfigMap {
+				configMapReferences = append(configMapReferences,
+					types.NamespacedName{
+						Namespace: btp.Namespace,
+						Name:      string(ro.Response.Body.ValueRef.Name),
+					}.String(),
+				)
+			}
+		}
+	}
+	return configMapReferences
+}
+
 // addBtlsIndexers adds indexing on BackendTLSPolicy, for ConfigMap objects that are
 // referenced in BackendTLSPolicy objects. This helps in querying for BackendTLSPolicies that are
 // affected by a particular ConfigMap CRUD.
diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go
index 9c4d582b5..a885d58ca 100644
--- a/internal/provider/kubernetes/predicates.go
+++ b/internal/provider/kubernetes/predicates.go
@@ -588,7 +588,7 @@ func (r *gatewayAPIReconciler) handleNode(obj client.Object) bool {
 	return true
 }
 
-// validateConfigMapForReconcile checks whether the ConfigMap belongs to a valid ClientTrafficPolicy.
+// validateConfigMapForReconcile checks whether the ConfigMap belongs to a valid EG resource.
 func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object) bool {
 	configMap, ok := obj.(*corev1.ConfigMap)
 	if !ok {
@@ -604,8 +604,8 @@ func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object)
 		return false
 	}
 
-	if len(ctpList.Items) == 0 {
-		return false
+	if len(ctpList.Items) > 0 {
+		return true
 	}
 
 	btlsList := &gwapiv1a3.BackendTLSPolicyList{}
@@ -616,11 +616,23 @@ func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object)
 		return false
 	}
 
-	if len(btlsList.Items) == 0 {
+	if len(btlsList.Items) > 0 {
+		return true
+	}
+
+	btpList := &egv1a1.BackendTrafficPolicyList{}
+	if err := r.client.List(context.Background(), btpList, &client.ListOptions{
+		FieldSelector: fields.OneTermEqualSelector(configMapBtpIndex, utils.NamespacedName(configMap).String()),
+	}); err != nil {
+		r.log.Error(err, "unable to find associated BackendTrafficPolicy")
 		return false
 	}
 
-	return true
+	if len(btpList.Items) > 0 {
+		return true
+	}
+
+	return false
 }
 
 func (r *gatewayAPIReconciler) isEnvoyExtensionPolicyReferencingBackend(nsName *types.NamespacedName) bool {
diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go
index 3a0a9f813..956a2b59b 100644
--- a/internal/provider/kubernetes/routes.go
+++ b/internal/provider/kubernetes/routes.go
@@ -56,8 +56,7 @@ func (r *gatewayAPIReconciler) processTLSRoutes(ctx context.Context, gatewayName
 
 		for _, rule := range tlsRoute.Spec.Rules {
 			for _, backendRef := range rule.BackendRefs {
-				ref := gatewayapi.UpgradeBackendRef(backendRef)
-				if err := validateBackendRef(&ref); err != nil {
+				if err := validateBackendRef(&backendRef); err != nil {
 					r.log.Error(err, "invalid backendRef")
 					continue
 				}
@@ -467,8 +466,7 @@ func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayName
 
 		for _, rule := range tcpRoute.Spec.Rules {
 			for _, backendRef := range rule.BackendRefs {
-				ref := gatewayapi.UpgradeBackendRef(backendRef)
-				if err := validateBackendRef(&ref); err != nil {
+				if err := validateBackendRef(&backendRef); err != nil {
 					r.log.Error(err, "invalid backendRef")
 					continue
 				}
@@ -545,8 +543,7 @@ func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayName
 
 		for _, rule := range udpRoute.Spec.Rules {
 			for _, backendRef := range rule.BackendRefs {
-				ref := gatewayapi.UpgradeBackendRef(backendRef)
-				if err := validateBackendRef(&ref); err != nil {
+				if err := validateBackendRef(&backendRef); err != nil {
 					r.log.Error(err, "invalid backendRef")
 					continue
 				}
diff --git a/internal/xds/translator/custom_response.go b/internal/xds/translator/custom_response.go
new file mode 100644
index 000000000..1d1bf3a5d
--- /dev/null
+++ b/internal/xds/translator/custom_response.go
@@ -0,0 +1,450 @@
+// 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.
+
+package translator
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+
+	cncfv3 "github.com/cncf/xds/go/xds/core/v3"
+	matcherv3 "github.com/cncf/xds/go/xds/type/matcher/v3"
+	typev3 "github.com/cncf/xds/go/xds/type/v3"
+	corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
+	respv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/custom_response/v3"
+	hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
+	policyv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/custom_response/local_response_policy/v3"
+	envoymatcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
+	expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+	"google.golang.org/protobuf/types/known/anypb"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
+	"github.com/envoyproxy/gateway/internal/xds/types"
+)
+
+func init() {
+	registerHTTPFilter(&customResponse{})
+}
+
+type customResponse struct{}
+
+var _ httpFilter = &customResponse{}
+
+// patchHCM builds and appends the customResponse Filters to the HTTP Connection Manager
+// if applicable, and it does not already exist.
+// Note: this method creates an customResponse filter for each route that contains an ResponseOverride config.
+// the filter is disabled by default. It is enabled on the route level.
+func (c *customResponse) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error {
+	var errs error
+
+	if mgr == nil {
+		return errors.New("hcm is nil")
+	}
+
+	if irListener == nil {
+		return errors.New("ir listener is nil")
+	}
+
+	for _, route := range irListener.Routes {
+		if !c.routeContainsResponseOverride(route) {
+			continue
+		}
+
+		// Only generates one CustomResponse Envoy filter for each unique name.
+		// For example, if there are two routes under the same gateway with the
+		// same CustomResponse config, only one CustomResponse filter will be generated.
+		if hcmContainsFilter(mgr, c.customResponseFilterName(route.Traffic.ResponseOverride)) {
+			continue
+		}
+
+		filter, err := c.buildHCMCustomResponseFilter(route.Traffic.ResponseOverride)
+		if err != nil {
+			errs = errors.Join(errs, err)
+			continue
+		}
+
+		mgr.HttpFilters = append(mgr.HttpFilters, filter)
+	}
+
+	return errs
+}
+
+// buildHCMCustomResponseFilter returns an OAuth2 HTTP filter from the provided IR HTTPRoute.
+func (c *customResponse) buildHCMCustomResponseFilter(ro *ir.ResponseOverride) (*hcmv3.HttpFilter, error) {
+	proto, err := c.customResponseConfig(ro)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := proto.ValidateAll(); err != nil {
+		return nil, err
+	}
+
+	any, err := anypb.New(proto)
+	if err != nil {
+		return nil, err
+	}
+
+	return &hcmv3.HttpFilter{
+		Name:     c.customResponseFilterName(ro),
+		Disabled: true,
+		ConfigType: &hcmv3.HttpFilter_TypedConfig{
+			TypedConfig: any,
+		},
+	}, nil
+}
+
+func (c *customResponse) customResponseFilterName(ro *ir.ResponseOverride) string {
+	return perRouteFilterName(egv1a1.EnvoyFilterCustomResponse, ro.Name)
+}
+
+func (c *customResponse) customResponseConfig(ro *ir.ResponseOverride) (*respv3.CustomResponse, error) {
+	var matchers []*matcherv3.Matcher_MatcherList_FieldMatcher
+
+	for _, r := range ro.Rules {
+		var (
+			action    *matcherv3.Matcher_OnMatch_Action
+			predicate *matcherv3.Matcher_MatcherList_Predicate
+			err       error
+		)
+
+		if action, err = c.buildAction(r); err != nil {
+			return nil, err
+		}
+
+		switch {
+		case len(r.Match.StatusCodes) == 0:
+			// This is just a sanity check, as the CRD validation should have caught this.
+			return nil, fmt.Errorf("missing status code in response override rule")
+		case len(r.Match.StatusCodes) == 1:
+			if predicate, err = c.buildSinglePredicate(r.Match.StatusCodes[0]); err != nil {
+				return nil, err
+			}
+
+			matcher := &matcherv3.Matcher_MatcherList_FieldMatcher{
+				Predicate: predicate,
+				OnMatch: &matcherv3.Matcher_OnMatch{
+					OnMatch: action,
+				},
+			}
+
+			matchers = append(matchers, matcher)
+		case len(r.Match.StatusCodes) > 1:
+			var predicates []*matcherv3.Matcher_MatcherList_Predicate
+
+			for _, codeMatch := range r.Match.StatusCodes {
+				if predicate, err = c.buildSinglePredicate(codeMatch); err != nil {
+					return nil, err
+				}
+
+				predicates = append(predicates, predicate)
+			}
+
+			// Create a single matcher that ORs all the predicates together.
+			// The rule will match if any of the codes match.
+			matcher := &matcherv3.Matcher_MatcherList_FieldMatcher{
+				Predicate: &matcherv3.Matcher_MatcherList_Predicate{
+					MatchType: &matcherv3.Matcher_MatcherList_Predicate_OrMatcher{
+						OrMatcher: &matcherv3.Matcher_MatcherList_Predicate_PredicateList{
+							Predicate: predicates,
+						},
+					},
+				},
+				OnMatch: &matcherv3.Matcher_OnMatch{
+					OnMatch: action,
+				},
+			}
+
+			matchers = append(matchers, matcher)
+		}
+
+	}
+
+	// Create a MatcherList.
+	// The rules will be evaluated in order, and the first match wins.
+	cr := &respv3.CustomResponse{
+		CustomResponseMatcher: &matcherv3.Matcher{
+			MatcherType: &matcherv3.Matcher_MatcherList_{
+				MatcherList: &matcherv3.Matcher_MatcherList{
+					Matchers: matchers,
+				},
+			},
+		},
+	}
+
+	return cr, nil
+}
+
+func (c *customResponse) buildSinglePredicate(codeMatch ir.StatusCodeMatch) (*matcherv3.Matcher_MatcherList_Predicate, error) {
+	var (
+		httpAttributeCELInput *cncfv3.TypedExtensionConfig
+		statusCodeInput       *cncfv3.TypedExtensionConfig
+		statusCodeCELMatcher  *cncfv3.TypedExtensionConfig
+		err                   error
+	)
+
+	// Use CEL to match a range of status codes.
+	if codeMatch.Range != nil {
+		if httpAttributeCELInput, err = c.buildHTTPAttributeCELInput(); err != nil {
+			return nil, err
+		}
+
+		if statusCodeCELMatcher, err = c.buildStatusCodeCELMatcher(*codeMatch.Range); err != nil {
+			return nil, err
+		}
+
+		return &matcherv3.Matcher_MatcherList_Predicate{
+			MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{
+				SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{
+					Input: httpAttributeCELInput,
+					Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{
+						CustomMatch: statusCodeCELMatcher,
+					},
+				},
+			},
+		}, nil
+	} else {
+		// Use exact string match to match a single status code.
+		if statusCodeInput, err = c.buildStatusCodeInput(); err != nil {
+			return nil, err
+		}
+
+		return &matcherv3.Matcher_MatcherList_Predicate{
+			MatchType: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_{
+				SinglePredicate: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate{
+					Input: statusCodeInput,
+					Matcher: &matcherv3.Matcher_MatcherList_Predicate_SinglePredicate_ValueMatch{
+						ValueMatch: &matcherv3.StringMatcher{
+							MatchPattern: &matcherv3.StringMatcher_Exact{
+								Exact: strconv.Itoa(*codeMatch.Value),
+							},
+						},
+					},
+				},
+			},
+		}, nil
+	}
+}
+
+func (c *customResponse) buildHTTPAttributeCELInput() (*cncfv3.TypedExtensionConfig, error) {
+	var (
+		pb  *anypb.Any
+		err error
+	)
+
+	if pb, err = anypb.New(&matcherv3.HttpAttributesCelMatchInput{}); err != nil {
+		return nil, err
+	}
+
+	return &cncfv3.TypedExtensionConfig{
+		Name:        "http-attributes-cel-match-input",
+		TypedConfig: pb,
+	}, nil
+}
+
+func (c *customResponse) buildStatusCodeInput() (*cncfv3.TypedExtensionConfig, error) {
+	var (
+		pb  *anypb.Any
+		err error
+	)
+
+	if pb, err = anypb.New(&envoymatcherv3.HttpResponseStatusCodeMatchInput{}); err != nil {
+		return nil, err
+	}
+
+	return &cncfv3.TypedExtensionConfig{
+		Name:        "http-response-status-code-match-input",
+		TypedConfig: pb,
+	}, nil
+}
+
+func (c *customResponse) buildStatusCodeCELMatcher(codeRange ir.StatusCodeRange) (*cncfv3.TypedExtensionConfig, error) {
+	var (
+		pb  *anypb.Any
+		err error
+	)
+
+	// Build the CEL expression AST: response.code >= codeRange.Start && response.code <= codeRange.End
+	matcher := &matcherv3.CelMatcher{
+		ExprMatch: &typev3.CelExpression{
+			ExprSpecifier: &typev3.CelExpression_ParsedExpr{
+				ParsedExpr: &expr.ParsedExpr{
+					Expr: &expr.Expr{
+						Id: 9,
+						ExprKind: &expr.Expr_CallExpr{
+							CallExpr: &expr.Expr_Call{
+								Function: "_&&_",
+								Args: []*expr.Expr{
+									{
+										Id: 3,
+										ExprKind: &expr.Expr_CallExpr{
+											CallExpr: &expr.Expr_Call{
+												Function: "_>=_",
+												Args: []*expr.Expr{
+													{
+														Id: 2,
+														ExprKind: &expr.Expr_SelectExpr{
+															SelectExpr: &expr.Expr_Select{
+																Operand: &expr.Expr{
+																	Id: 1,
+																	ExprKind: &expr.Expr_IdentExpr{
+																		IdentExpr: &expr.Expr_Ident{
+																			Name: "response",
+																		},
+																	},
+																},
+																Field: "code",
+															},
+														},
+													},
+													{
+														Id: 4,
+														ExprKind: &expr.Expr_ConstExpr{
+															ConstExpr: &expr.Constant{
+																ConstantKind: &expr.Constant_Int64Value{
+																	Int64Value: int64(codeRange.Start),
+																},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+									{
+										Id: 7,
+										ExprKind: &expr.Expr_CallExpr{
+											CallExpr: &expr.Expr_Call{
+												Function: "_<=_",
+												Args: []*expr.Expr{
+													{
+														Id: 6,
+														ExprKind: &expr.Expr_SelectExpr{
+															SelectExpr: &expr.Expr_Select{
+																Operand: &expr.Expr{
+																	Id: 5,
+																	ExprKind: &expr.Expr_IdentExpr{
+																		IdentExpr: &expr.Expr_Ident{
+																			Name: "response",
+																		},
+																	},
+																},
+																Field: "code",
+															},
+														},
+													},
+													{
+														Id: 8,
+														ExprKind: &expr.Expr_ConstExpr{
+															ConstExpr: &expr.Constant{
+																ConstantKind: &expr.Constant_Int64Value{
+																	Int64Value: int64(codeRange.End),
+																},
+															},
+														},
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	if err := matcher.ValidateAll(); err != nil {
+		return nil, err
+	}
+
+	if pb, err = anypb.New(matcher); err != nil {
+		return nil, err
+	}
+
+	return &cncfv3.TypedExtensionConfig{
+		Name:        "cel-matcher",
+		TypedConfig: pb,
+	}, nil
+}
+
+func (c *customResponse) buildAction(r ir.ResponseOverrideRule) (*matcherv3.Matcher_OnMatch_Action, error) {
+	response := &policyv3.LocalResponsePolicy{
+		Body: &corev3.DataSource{
+			Specifier: &corev3.DataSource_InlineString{
+				InlineString: r.Response.Body,
+			},
+		},
+	}
+
+	if r.Response.ContentType != nil && *r.Response.ContentType != "" {
+		response.ResponseHeadersToAdd = append(response.ResponseHeadersToAdd, &corev3.HeaderValueOption{
+			Header: &corev3.HeaderValue{
+				Key:   "Content-Type",
+				Value: *r.Response.ContentType,
+			},
+			AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
+		})
+	}
+
+	var (
+		pb  *anypb.Any
+		err error
+	)
+
+	if err := response.ValidateAll(); err != nil {
+		return nil, err
+	}
+
+	if pb, err = anypb.New(response); err != nil {
+		return nil, err
+	}
+
+	return &matcherv3.Matcher_OnMatch_Action{
+		Action: &cncfv3.TypedExtensionConfig{
+			Name:        r.Name,
+			TypedConfig: pb,
+		},
+	}, nil
+}
+
+// routeContainsResponseOverride returns true if ResponseOverride exists for the provided route.
+func (c *customResponse) routeContainsResponseOverride(irRoute *ir.HTTPRoute) bool {
+	if irRoute != nil &&
+		irRoute.Traffic != nil &&
+		irRoute.Traffic.ResponseOverride != nil {
+		return true
+	}
+	return false
+}
+
+func (c *customResponse) patchResources(tCtx *types.ResourceVersionTable,
+	routes []*ir.HTTPRoute,
+) error {
+	return nil
+}
+
+// patchRoute patches the provided route with the customResponse config if applicable.
+// Note: this method enables the corresponding customResponse filter for the provided route.
+func (c *customResponse) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error {
+	if route == nil {
+		return errors.New("xds route is nil")
+	}
+	if irRoute == nil {
+		return errors.New("ir route is nil")
+	}
+	if irRoute.Traffic == nil || irRoute.Traffic.ResponseOverride == nil {
+		return nil
+	}
+	filterName := c.customResponseFilterName(irRoute.Traffic.ResponseOverride)
+	if err := enableFilterOnRoute(route, filterName); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go
index 963b7c804..e4e7b4a02 100644
--- a/internal/xds/translator/oidc.go
+++ b/internal/xds/translator/oidc.go
@@ -53,9 +53,9 @@ func (*oidc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListe
 			continue
 		}
 
-		// Only generates one BasicAuth Envoy filter for each unique name.
+		// Only generates one OAuth2 Envoy filter for each unique name.
 		// For example, if there are two routes under the same gateway with the
-		// same BasicAuth config, only one BasicAuth filter will be generated.
+		// same OAuth2 config, only one OAuth2 filter will be generated.
 		if hcmContainsFilter(mgr, oauth2FilterName(route.Security.OIDC)) {
 			continue
 		}
diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go
index 660bc2a7d..06b37bc45 100644
--- a/internal/xds/translator/ratelimit.go
+++ b/internal/xds/translator/ratelimit.go
@@ -157,11 +157,12 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 		// Matches are ANDed
 		rlActions := []*routev3.RateLimit_Action{routeDescriptor}
 		for mIdx, match := range rule.HeaderMatches {
+			var action *routev3.RateLimit_Action
 			// Case for distinct match
 			if match.Distinct {
 				// Setup RequestHeader actions
 				descriptorKey := getRouteRuleDescriptor(rIdx, mIdx)
-				action := &routev3.RateLimit_Action{
+				action = &routev3.RateLimit_Action{
 					ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{
 						RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{
 							HeaderName:    match.Name,
@@ -169,7 +170,6 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 						},
 					},
 				}
-				rlActions = append(rlActions, action)
 			} else {
 				// Setup HeaderValueMatch actions
 				descriptorKey := getRouteRuleDescriptor(rIdx, mIdx)
@@ -184,7 +184,7 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 				if match.Invert != nil && *match.Invert {
 					expectMatch = false
 				}
-				action := &routev3.RateLimit_Action{
+				action = &routev3.RateLimit_Action{
 					ActionSpecifier: &routev3.RateLimit_Action_HeaderValueMatch_{
 						HeaderValueMatch: &routev3.RateLimit_Action_HeaderValueMatch{
 							DescriptorKey:   descriptorKey,
@@ -196,8 +196,8 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 						},
 					},
 				}
-				rlActions = append(rlActions, action)
 			}
+			rlActions = append(rlActions, action)
 		}
 
 		// To be able to rate limit each individual IP, we need to use a nested descriptors structure in the configuration
@@ -236,7 +236,7 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 			// Setup RemoteAddress action if distinct match is set
 			if rule.CIDRMatch.Distinct {
 				// Setup RemoteAddress action
-				action := &routev3.RateLimit_Action{
+				action = &routev3.RateLimit_Action{
 					ActionSpecifier: &routev3.RateLimit_Action_RemoteAddress_{
 						RemoteAddress: &routev3.RateLimit_Action_RemoteAddress{},
 					},
@@ -245,8 +245,8 @@ func buildRouteRateLimits(descriptorPrefix string, global *ir.GlobalRateLimit) [
 			}
 		}
 
-		// Case when header match is not set and the rate limit is applied
-		// to all traffic.
+		// Case when both header and cidr match are not set and the ratelimit
+		// will be applied to all traffic.
 		if !rule.IsMatchSet() {
 			// Setup GenericKey action
 			action := &routev3.RateLimit_Action{
@@ -333,22 +333,21 @@ func BuildRateLimitServiceConfig(irListener *ir.HTTPListener) *rlsconfv3.RateLim
 func buildRateLimitServiceDescriptors(global *ir.GlobalRateLimit) []*rlsconfv3.RateLimitDescriptor {
 	pbDescriptors := make([]*rlsconfv3.RateLimitDescriptor, 0, len(global.Rules))
 
+	// The order in which matching descriptors are built is consistent with
+	// the order in which ratelimit actions are built:
+	//  1) Header Matches
+	//  2) CIDR Match
+	//  3) No Match
 	for rIdx, rule := range global.Rules {
-		var head, cur *rlsconfv3.RateLimitDescriptor
-		if !rule.IsMatchSet() {
-			pbDesc := new(rlsconfv3.RateLimitDescriptor)
-			// GenericKey case
-			pbDesc.Key = getRouteRuleDescriptor(rIdx, -1)
-			pbDesc.Value = getRouteRuleDescriptor(rIdx, -1)
-			rateLimit := rlsconfv3.RateLimitPolicy{
-				RequestsPerUnit: uint32(rule.Limit.Requests),
-				Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
-			}
-			pbDesc.RateLimit = &rateLimit
-			head = pbDesc
-			cur = head
+		rateLimitPolicy := &rlsconfv3.RateLimitPolicy{
+			RequestsPerUnit: uint32(rule.Limit.Requests),
+			Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
 		}
 
+		// We use a chain structure to describe the matching descriptors for one rule.
+		// The RateLimitPolicy should be added to the last descriptor in the chain.
+		var head, cur *rlsconfv3.RateLimitDescriptor
+
 		for mIdx, match := range rule.HeaderMatches {
 			pbDesc := new(rlsconfv3.RateLimitDescriptor)
 			// Case for distinct match
@@ -361,15 +360,6 @@ func buildRateLimitServiceDescriptors(global *ir.GlobalRateLimit) []*rlsconfv3.R
 				pbDesc.Value = getRouteRuleDescriptor(rIdx, mIdx)
 			}
 
-			// Add the ratelimit values to the last descriptor
-			if mIdx == len(rule.HeaderMatches)-1 {
-				rateLimit := rlsconfv3.RateLimitPolicy{
-					RequestsPerUnit: uint32(rule.Limit.Requests),
-					Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
-				}
-				pbDesc.RateLimit = &rateLimit
-			}
-
 			if mIdx == 0 {
 				head = pbDesc
 			} else {
@@ -377,6 +367,9 @@ func buildRateLimitServiceDescriptors(global *ir.GlobalRateLimit) []*rlsconfv3.R
 			}
 
 			cur = pbDesc
+
+			// Do not add the RateLimitPolicy to the last header match descriptor yet,
+			// as it is also possible that CIDR match descriptor also exist.
 		}
 
 		// EG supports two kinds of rate limit descriptors for the source IP: exact and distinct.
@@ -405,25 +398,37 @@ func buildRateLimitServiceDescriptors(global *ir.GlobalRateLimit) []*rlsconfv3.R
 			pbDesc := new(rlsconfv3.RateLimitDescriptor)
 			pbDesc.Key = "masked_remote_address"
 			pbDesc.Value = rule.CIDRMatch.CIDR
-			rateLimit := rlsconfv3.RateLimitPolicy{
-				RequestsPerUnit: uint32(rule.Limit.Requests),
-				Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
+
+			if cur != nil {
+				// The header match descriptor chain exist, add current
+				// descriptor to the chain.
+				cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
+			} else {
+				head = pbDesc
 			}
+			cur = pbDesc
 
 			if rule.CIDRMatch.Distinct {
-				pbDesc.Descriptors = []*rlsconfv3.RateLimitDescriptor{
-					{
-						Key:       "remote_address",
-						RateLimit: &rateLimit,
-					},
-				}
-			} else {
-				pbDesc.RateLimit = &rateLimit
+				pbDesc := new(rlsconfv3.RateLimitDescriptor)
+				pbDesc.Key = "remote_address"
+				cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
+				cur = pbDesc
 			}
+		}
+
+		// Case when both header and cidr match are not set and the ratelimit
+		// will be applied to all traffic.
+		if !rule.IsMatchSet() {
+			pbDesc := new(rlsconfv3.RateLimitDescriptor)
+			// GenericKey case
+			pbDesc.Key = getRouteRuleDescriptor(rIdx, -1)
+			pbDesc.Value = getRouteRuleDescriptor(rIdx, -1)
 			head = pbDesc
 			cur = head
 		}
 
+		// Add the ratelimit policy to the last descriptor of chain.
+		cur.RateLimit = rateLimitPolicy
 		pbDescriptors = append(pbDescriptors, head)
 	}
 
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
new file mode 100644
index 000000000..524802636
--- /dev/null
+++ b/release-notes/current.yaml
@@ -0,0 +1,29 @@
+date: Pending
+
+# Changes that are expected to cause an incompatibility with previous versions, such as deletions or modifications to existing APIs.
+breaking changes: |
+  - Add a breaking change here
+
+# Updates addressing vulnerabilities, security flaws, or compliance requirements.
+security updates: |
+  - Add a security update here
+
+# New features or capabilities added in this release.
+new features: |
+  - Add a new feature here
+
+# Fixes for bugs identified in previous versions.
+bug fixes: |
+  - Add a bug fix here
+
+# Enhancements that improve performance.
+performance improvements: |
+  - Add a performance improvement here
+
+# Deprecated features or APIs.
+deprecations: |
+  - Add a deprecation here
+
+# Other notable changes not covered by the above sections.
+Other changes: |
+  - Add other changes here
diff --git a/site/content/en/contributions/RELEASING.md b/site/content/en/contributions/RELEASING.md
index 319bb73d0..70fff1c08 100644
--- a/site/content/en/contributions/RELEASING.md
+++ b/site/content/en/contributions/RELEASING.md
@@ -29,7 +29,13 @@ export GITHUB_REMOTE=origin
 ```
 
 1. Clone the repo, checkout the `main` branch, ensure it’s up-to-date, and your local branch is clean.
-2. Create a topic branch for adding the release notes and updating the [VERSION][] file with the release version. Refer to previous [release notes][] and [VERSION][] for additional details.
+2. Create a topic branch for adding the release notes and updating the [VERSION][] file with the release version. Refer to previous [release notes][] and [VERSION][] for additional details. The latest changes are already accumulated in the current.yaml file. Copy the content of the current.yaml file to the release notes file and clear the current.yaml file.
+
+   ```shell
+   echo "${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER}" > VERSION
+   ```
+
+   __Note:__ The release candidate version should be in the format `${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER}`.
 3. Sign, commit, and push your changes to your fork.
 4. Submit a [Pull Request][] to merge the changes into the `main` branch. Do not proceed until your PR has merged and
    the [Build and Test][] has successfully completed.
@@ -106,7 +112,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update the `Documentation` referred link on the menu in `site/hugo.toml`:
-   
+
       **DON'T FORGOT TO MOVE IT UNDER `LATEST`**
 
       ```shell
@@ -118,7 +124,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update `site/layouts/shortcodes/helm-version.html` base on latest minor version.
-   
+
       ```console
       {{- $pagePrefix := (index (split $.Page.File.Dir "/") 0) -}}
       {{- with (eq $pagePrefix "latest") -}}
@@ -133,7 +139,7 @@ export GITHUB_REMOTE=origin
       ```
 
    1. Update `site/layouts/shortcodes/yaml-version.html` base on latest minor version.
-      
+
       ```console
       {{- $pagePrefix := (index (split $.Page.File.Dir "/") 0) -}}
       {{- with (eq $pagePrefix "latest") -}}
diff --git a/site/content/en/docs/tasks/extensibility/wasm.md b/site/content/en/docs/tasks/extensibility/wasm.md
index cb2e013dd..1b1d32f9e 100644
--- a/site/content/en/docs/tasks/extensibility/wasm.md
+++ b/site/content/en/docs/tasks/extensibility/wasm.md
@@ -23,7 +23,7 @@ kubectl get gateway/eg -o yaml
 
 ## Configuration
 
-Envoy Gateway supports two types of Wasm extensions: 
+Envoy Gateway supports two types of Wasm extensions:
 * HTTP Wasm Extension: The Wasm extension is fetched from a remote URL.
 * Image Wasm Extension: The Wasm extension is packaged as an OCI image and fetched from an image registry.
 
@@ -54,7 +54,7 @@ spec:
     code:
       type: HTTP
       http:
-        url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+        url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
         sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 EOF
 ```
@@ -80,7 +80,7 @@ spec:
       code:
         type: HTTP
         http:
-          url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+          url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
           sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 ```
 
diff --git a/site/content/en/v1.1/tasks/extensibility/wasm.md b/site/content/en/v1.1/tasks/extensibility/wasm.md
index cb2e013dd..1b1d32f9e 100644
--- a/site/content/en/v1.1/tasks/extensibility/wasm.md
+++ b/site/content/en/v1.1/tasks/extensibility/wasm.md
@@ -23,7 +23,7 @@ kubectl get gateway/eg -o yaml
 
 ## Configuration
 
-Envoy Gateway supports two types of Wasm extensions: 
+Envoy Gateway supports two types of Wasm extensions:
 * HTTP Wasm Extension: The Wasm extension is fetched from a remote URL.
 * Image Wasm Extension: The Wasm extension is packaged as an OCI image and fetched from an image registry.
 
@@ -54,7 +54,7 @@ spec:
     code:
       type: HTTP
       http:
-        url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+        url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
         sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 EOF
 ```
@@ -80,7 +80,7 @@ spec:
       code:
         type: HTTP
         http:
-          url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+          url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
           sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 ```
 


Test patch:
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.in.yaml
new file mode 100644
index 000000000..e44a8473d
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.in.yaml
@@ -0,0 +1,141 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: default
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: default
+      name: gateway-2
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+grpcRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: GRPCRoute
+    metadata:
+      namespace: default
+      name: grpcroute-1
+    spec:
+      parentRefs:
+        - namespace: default
+          name: gateway-1
+          sectionName: http
+      rules:
+        - backendRefs:
+            - name: service-1
+              port: 8080
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: default
+          name: gateway-2
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+configMaps:
+  - apiVersion: v1
+    kind: ConfigMap
+    metadata:
+      name: response-override-config
+      namespace: default
+    data: {}
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-gateway
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+      responseOverride:
+        - match:
+            statusCodes:
+              - type: Value
+                value: 404
+          response:
+            contentType: text/plain
+            body:
+              type: Inline
+              inline: "gateway-1 Not Found"
+        - match:
+            statusCodes:
+              - type: Value
+                value: 500
+              - type: Range
+                range:
+                  start: 501
+                  end: 511
+          response:
+            contentType: application/json
+            body:
+              type: ValueRef
+              valueRef:
+                group: ""
+                kind: ConfigMap
+                name: response-override-config
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      responseOverride:
+        - match:
+            statusCodes:
+              - value: 404
+          response:
+            contentType: text/plain
+            body:
+              inline: "httproute-1 Not Found"
+        - match:
+            statusCodes:
+              - value: 500
+              - type: Range
+                range:
+                  start: 501
+                  end: 511
+          response:
+            contentType: application/json
+            body:
+              type: ValueRef
+              valueRef:
+                group: ""
+                kind: ConfigMap
+                name: response-override-config-1
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.out.yaml
new file mode 100644
index 000000000..c1542d9ca
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override-invalid-valueref.out.yaml
@@ -0,0 +1,371 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    responseOverride:
+    - match:
+        statusCodes:
+        - type: null
+          value: 404
+      response:
+        body:
+          inline: httproute-1 Not Found
+          type: null
+        contentType: text/plain
+    - match:
+        statusCodes:
+        - type: null
+          value: 500
+        - range:
+            end: 511
+            start: 501
+          type: Range
+      response:
+        body:
+          type: ValueRef
+          valueRef:
+            group: ""
+            kind: ConfigMap
+            name: response-override-config-1
+        contentType: application/json
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-2
+        namespace: default
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: 'ResponseOverride: can''t find the referenced configmap response-override-config-1.'
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: default
+  spec:
+    responseOverride:
+    - match:
+        statusCodes:
+        - type: Value
+          value: 404
+      response:
+        body:
+          inline: gateway-1 Not Found
+          type: Inline
+        contentType: text/plain
+    - match:
+        statusCodes:
+        - type: Value
+          value: 500
+        - range:
+            end: 511
+            start: 501
+          type: Range
+      response:
+        body:
+          type: ValueRef
+          valueRef:
+            group: ""
+            kind: ConfigMap
+            name: response-override-config
+        contentType: application/json
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+      conditions:
+      - lastTransitionTime: null
+        message: 'ResponseOverride: can''t find the key response.body in the referenced
+          configmap response-override-config.'
+        reason: Invalid
+        status: "False"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: default
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      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
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-2
+    namespace: default
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      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
+grpcRoutes:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    creationTimestamp: null
+    name: grpcroute-1
+    namespace: default
+  spec:
+    parentRefs:
+    - name: gateway-1
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+  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: default
+        sectionName: http
+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-2
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  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-2
+        namespace: default
+        sectionName: http
+infraIR:
+  default/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: default/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: default
+      name: default/gateway-1
+  default/gateway-2:
+    proxy:
+      listeners:
+      - address: null
+        name: default/gateway-2/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-2
+          gateway.envoyproxy.io/owning-gateway-namespace: default
+      name: default/gateway-2
+xdsIR:
+  default/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: true
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+        sectionName: http
+      name: default/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: grpcroute/default/grpcroute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: GRPC
+            weight: 1
+        directResponse:
+          statusCode: 500
+        hostname: '*'
+        isHTTP2: true
+        metadata:
+          kind: GRPCRoute
+          name: grpcroute-1
+          namespace: default
+        name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+  default/gateway-2:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-2
+        namespace: default
+        sectionName: http
+      name: default/gateway-2/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        directResponse:
+          statusCode: 500
+        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: /
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.in.yaml
new file mode 100644
index 000000000..51dd9fd71
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.in.yaml
@@ -0,0 +1,145 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: default
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: default
+      name: gateway-2
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+grpcRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: GRPCRoute
+    metadata:
+      namespace: default
+      name: grpcroute-1
+    spec:
+      parentRefs:
+        - namespace: default
+          name: gateway-1
+          sectionName: http
+      rules:
+        - backendRefs:
+            - name: service-1
+              port: 8080
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: default
+          name: gateway-2
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+configMaps:
+  - apiVersion: v1
+    kind: ConfigMap
+    metadata:
+      name: response-override-config
+      namespace: default
+    data:
+      response.body: |
+        {
+          "error": "Internal Server Error"
+        }
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-gateway
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+      responseOverride:
+        - match:
+            statusCodes:
+              - type: Value
+                value: 404
+          response:
+            contentType: text/plain
+            body:
+              type: Inline
+              inline: "gateway-1 Not Found"
+        - match:
+            statusCodes:
+              - type: Value
+                value: 500
+              - type: Range
+                range:
+                  start: 501
+                  end: 511
+          response:
+            contentType: application/json
+            body:
+              type: ValueRef
+              valueRef:
+                group: ""
+                kind: ConfigMap
+                name: response-override-config
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      responseOverride:
+        - match:
+            statusCodes:
+              - value: 404
+          response:
+            contentType: text/plain
+            body:
+              inline: "httproute-1 Not Found"
+        - match:
+            statusCodes:
+              - value: 500
+              - type: Range
+                range:
+                  start: 501
+                  end: 511
+          response:
+            contentType: application/json
+            body:
+              type: ValueRef
+              valueRef:
+                group: ""
+                kind: ConfigMap
+                name: response-override-config
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml
new file mode 100644
index 000000000..568a57af4
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml
@@ -0,0 +1,414 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    responseOverride:
+    - match:
+        statusCodes:
+        - type: null
+          value: 404
+      response:
+        body:
+          inline: httproute-1 Not Found
+          type: null
+        contentType: text/plain
+    - match:
+        statusCodes:
+        - type: null
+          value: 500
+        - range:
+            end: 511
+            start: 501
+          type: Range
+      response:
+        body:
+          type: ValueRef
+          valueRef:
+            group: ""
+            kind: ConfigMap
+            name: response-override-config
+        contentType: application/json
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-2
+        namespace: default
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: default
+  spec:
+    responseOverride:
+    - match:
+        statusCodes:
+        - type: Value
+          value: 404
+      response:
+        body:
+          inline: gateway-1 Not Found
+          type: Inline
+        contentType: text/plain
+    - match:
+        statusCodes:
+        - type: Value
+          value: 500
+        - range:
+            end: 511
+            start: 501
+          type: Range
+      response:
+        body:
+          type: ValueRef
+          valueRef:
+            group: ""
+            kind: ConfigMap
+            name: response-override-config
+        contentType: application/json
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: default
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      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
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-2
+    namespace: default
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      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
+grpcRoutes:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    creationTimestamp: null
+    name: grpcroute-1
+    namespace: default
+  spec:
+    parentRefs:
+    - name: gateway-1
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+  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: default
+        sectionName: http
+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-2
+      namespace: default
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  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-2
+        namespace: default
+        sectionName: http
+infraIR:
+  default/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: default/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: default
+      name: default/gateway-1
+  default/gateway-2:
+    proxy:
+      listeners:
+      - address: null
+        name: default/gateway-2/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-2
+          gateway.envoyproxy.io/owning-gateway-namespace: default
+      name: default/gateway-2
+xdsIR:
+  default/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: true
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: default
+        sectionName: http
+      name: default/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: grpcroute/default/grpcroute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: GRPC
+            weight: 1
+        hostname: '*'
+        isHTTP2: true
+        metadata:
+          kind: GRPCRoute
+          name: grpcroute-1
+          namespace: default
+        name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          responseOverride:
+            name: backendtrafficpolicy/default/policy-for-gateway
+            rules:
+            - match:
+                statusCodes:
+                - value: 404
+              name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/0
+              response:
+                body: gateway-1 Not Found
+                contentType: text/plain
+            - match:
+                statusCodes:
+                - value: 500
+                - range:
+                    end: 511
+                    start: 501
+              name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/1
+              response:
+                body: |
+                  {
+                    "error": "Internal Server Error"
+                  }
+                contentType: application/json
+  default/gateway-2:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-2
+        namespace: default
+        sectionName: http
+      name: default/gateway-2/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - 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: /
+        traffic:
+          responseOverride:
+            name: backendtrafficpolicy/default/policy-for-route
+            rules:
+            - match:
+                statusCodes:
+                - value: 404
+              name: backendtrafficpolicy/default/policy-for-route/responseoverride/rule/0
+              response:
+                body: httproute-1 Not Found
+                contentType: text/plain
+            - match:
+                statusCodes:
+                - value: 500
+                - range:
+                    end: 511
+                    start: 501
+              name: backendtrafficpolicy/default/policy-for-route/responseoverride/rule/1
+              response:
+                body: |
+                  {
+                    "error": "Internal Server Error"
+                  }
+                contentType: application/json
diff --git a/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml
new file mode 100644
index 000000000..481b85986
--- /dev/null
+++ b/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml
@@ -0,0 +1,38 @@
+name: "first-listener"
+address: "0.0.0.0"
+port: 10080
+hostnames:
+- "*"
+path:
+  mergeSlashes: true
+  escapedSlashesAction: UnescapeAndRedirect
+routes:
+- name: "first-route"
+  traffic:
+    rateLimit:
+      global:
+        rules:
+        - headerMatches:
+          - name: "x-user-id"
+            exact: "one"
+          - name: "x-user-id"
+            exact: "two"
+          - name: "x-org-id"
+            exact: "three"
+          cidrMatch:
+            cidr: 0.0.0.0/0
+            ip: 0.0.0.0
+            maskLen: 0
+            isIPv6: false
+            distinct: false
+          limit:
+            requests: 5
+            unit: second
+  pathMatch:
+    exact: "foo/bar"
+  destination:
+    name: "first-route-dest"
+    settings:
+    - endpoints:
+      - host: "1.2.3.4"
+        port: 50000
diff --git a/internal/xds/translator/testdata/in/xds-ir/custom-response.yaml b/internal/xds/translator/testdata/in/xds-ir/custom-response.yaml
new file mode 100644
index 000000000..cb00ac65a
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/custom-response.yaml
@@ -0,0 +1,56 @@
+http:
+  - address: 0.0.0.0
+    hostnames:
+      - "*"
+    isHTTP2: false
+    metadata:
+      kind: Gateway
+      name: gateway-1
+      namespace: default
+      sectionName: http
+    name: default/gateway-1/http
+    path:
+      escapedSlashesAction: UnescapeAndRedirect
+      mergeSlashes: true
+    port: 10080
+    routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+            - addressType: IP
+              endpoints:
+                - host: 7.7.7.7
+                  port: 8080
+              protocol: HTTP
+              weight: 1
+        hostname: "*"
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/-1/*
+        traffic:
+          responseOverride:
+            name: backendtrafficpolicy/default/policy-for-gateway
+            rules:
+              - match:
+                  statusCodes:
+                    - value: 404
+                name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/0
+                response:
+                  body: gateway-1 Not Found
+                  contentType: text/plain
+              - match:
+                  statusCodes:
+                    - value: 500
+                    - range:
+                        end: 511
+                        start: 501
+                name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/1
+                response:
+                  body: |
+                    {
+                      "error": "Internal Server Error"
+                    }
+                  contentType: application/json
diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-headers-and-cidr.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-headers-and-cidr.yaml
new file mode 100644
index 000000000..fa9b6f31a
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-headers-and-cidr.yaml
@@ -0,0 +1,88 @@
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "first-route"
+    hostname: "*"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+            limit:
+              requests: 5
+              unit: second
+    destination:
+      name: "first-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+  - name: "second-route"
+    hostname: "*"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              distinct: true
+            - name: "foobar"
+              distinct: true
+            cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+            limit:
+              requests: 5
+              unit: second
+    pathMatch:
+      exact: "example"
+    destination:
+      name: "second-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+  - name: "third-route"
+    hostname: "*"
+    traffic:
+      rateLimit:
+        global:
+          rules:
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "one"
+            cidrMatch:
+              cidr: 192.168.0.0/16
+              maskLen: 16
+            limit:
+              requests: 5
+              unit: second
+          - headerMatches:
+            - name: "x-user-id"
+              exact: "two"
+            - name: "foobar"
+              distinct: true
+            cidrMatch:
+              cidr: 192.169.0.0/16
+              maskLen: 16
+            limit:
+              requests: 10
+              unit: second
+    destination:
+      name: "third-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
diff --git a/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.yaml b/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.yaml
new file mode 100644
index 000000000..83f5376da
--- /dev/null
+++ b/internal/xds/translator/testdata/out/ratelimit-config/header-and-cidr-matches.yaml
@@ -0,0 +1,38 @@
+name: first-listener
+domain: first-listener
+descriptors:
+  - key: first-route
+    value: first-route
+    rate_limit: null
+    descriptors:
+      - key: rule-0-match-0
+        value: rule-0-match-0
+        rate_limit: null
+        descriptors:
+          - key: rule-0-match-1
+            value: rule-0-match-1
+            rate_limit: null
+            descriptors:
+              - key: rule-0-match-2
+                value: rule-0-match-2
+                rate_limit: null
+                descriptors:
+                  - key: masked_remote_address
+                    value: 0.0.0.0/0
+                    rate_limit:
+                      requests_per_unit: 5
+                      unit: SECOND
+                      unlimited: false
+                      name: ""
+                      replaces: []
+                    descriptors: []
+                    shadow_mode: false
+                    detailed_metric: false
+                shadow_mode: false
+                detailed_metric: false
+            shadow_mode: false
+            detailed_metric: false
+        shadow_mode: false
+        detailed_metric: false
+    shadow_mode: false
+    detailed_metric: false
diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-response.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-response.clusters.yaml
new file mode 100644
index 000000000..9714612e3
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/custom-response.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-response.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-response.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/custom-response.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-response.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-response.listeners.yaml
new file mode 100644
index 000000000..19c565869
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/custom-response.listeners.yaml
@@ -0,0 +1,130 @@
+- 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:
+        - disabled: true
+          name: envoy.filters.http.custom_response/backendtrafficpolicy/default/policy-for-gateway
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.custom_response.v3.CustomResponse
+            customResponseMatcher:
+              matcherList:
+                matchers:
+                - onMatch:
+                    action:
+                      name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/0
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.http.custom_response.local_response_policy.v3.LocalResponsePolicy
+                        body:
+                          inlineString: gateway-1 Not Found
+                        responseHeadersToAdd:
+                        - appendAction: OVERWRITE_IF_EXISTS_OR_ADD
+                          header:
+                            key: Content-Type
+                            value: text/plain
+                  predicate:
+                    singlePredicate:
+                      input:
+                        name: http-response-status-code-match-input
+                        typedConfig:
+                          '@type': type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput
+                      valueMatch:
+                        exact: "404"
+                - onMatch:
+                    action:
+                      name: backendtrafficpolicy/default/policy-for-gateway/responseoverride/rule/1
+                      typedConfig:
+                        '@type': type.googleapis.com/envoy.extensions.http.custom_response.local_response_policy.v3.LocalResponsePolicy
+                        body:
+                          inlineString: |
+                            {
+                              "error": "Internal Server Error"
+                            }
+                        responseHeadersToAdd:
+                        - appendAction: OVERWRITE_IF_EXISTS_OR_ADD
+                          header:
+                            key: Content-Type
+                            value: application/json
+                  predicate:
+                    orMatcher:
+                      predicate:
+                      - singlePredicate:
+                          input:
+                            name: http-response-status-code-match-input
+                            typedConfig:
+                              '@type': type.googleapis.com/envoy.type.matcher.v3.HttpResponseStatusCodeMatchInput
+                          valueMatch:
+                            exact: "500"
+                      - singlePredicate:
+                          customMatch:
+                            name: cel-matcher
+                            typedConfig:
+                              '@type': type.googleapis.com/xds.type.matcher.v3.CelMatcher
+                              exprMatch:
+                                parsedExpr:
+                                  expr:
+                                    callExpr:
+                                      args:
+                                      - callExpr:
+                                          args:
+                                          - id: "2"
+                                            selectExpr:
+                                              field: code
+                                              operand:
+                                                id: "1"
+                                                identExpr:
+                                                  name: response
+                                          - constExpr:
+                                              int64Value: "501"
+                                            id: "4"
+                                          function: _>=_
+                                        id: "3"
+                                      - callExpr:
+                                          args:
+                                          - id: "6"
+                                            selectExpr:
+                                              field: code
+                                              operand:
+                                                id: "5"
+                                                identExpr:
+                                                  name: response
+                                          - constExpr:
+                                              int64Value: "511"
+                                            id: "8"
+                                          function: _<=_
+                                        id: "7"
+                                      function: _&&_
+                                    id: "9"
+                          input:
+                            name: http-attributes-cel-match-input
+                            typedConfig:
+                              '@type': type.googleapis.com/xds.type.matcher.v3.HttpAttributesCelMatchInput
+        - 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: default/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: default/gateway-1/http
+  name: default/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/custom-response.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/custom-response.routes.yaml
new file mode 100644
index 000000000..8262bb6f3
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/custom-response.routes.yaml
@@ -0,0 +1,33 @@
+- ignorePortInHostMatching: true
+  name: default/gateway-1/http
+  virtualHosts:
+  - domains:
+    - '*'
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: default
+            sectionName: http
+    name: default/gateway-1/http/*
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/-1/*
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+      typedPerFilterConfig:
+        envoy.filters.http.custom_response/backendtrafficpolicy/default/policy-for-gateway:
+          '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig
+          config: {}
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.clusters.yaml
new file mode 100644
index 000000000..0ba174907
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.clusters.yaml
@@ -0,0 +1,98 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: first-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: first-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: second-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: second-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: third-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: third-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  dnsRefreshRate: 30s
+  lbPolicy: LEAST_REQUEST
+  loadAssignment:
+    clusterName: ratelimit_cluster
+    endpoints:
+    - lbEndpoints:
+      - endpoint:
+          address:
+            socketAddress:
+              address: envoy-ratelimit.envoy-gateway-system.svc.cluster.local
+              portValue: 8081
+        loadBalancingWeight: 1
+      loadBalancingWeight: 1
+      locality:
+        region: ratelimit_cluster/backend/0
+  name: ratelimit_cluster
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  respectDnsTtl: true
+  transportSocket:
+    name: envoy.transport_sockets.tls
+    typedConfig:
+      '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
+      commonTlsContext:
+        tlsCertificates:
+        - certificateChain:
+            filename: /certs/tls.crt
+          privateKey:
+            filename: /certs/tls.key
+        validationContext:
+          trustedCa:
+            filename: /certs/ca.crt
+  type: STRICT_DNS
+  typedExtensionProtocolOptions:
+    envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+      '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+      explicitHttpConfig:
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.endpoints.yaml
new file mode 100644
index 000000000..475b89a08
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.endpoints.yaml
@@ -0,0 +1,36 @@
+- clusterName: first-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: first-route-dest/backend/0
+- clusterName: second-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: second-route-dest/backend/0
+- clusterName: third-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: third-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.listeners.yaml
new file mode 100644
index 000000000..a80f448f0
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.listeners.yaml
@@ -0,0 +1,44 @@
+- 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.ratelimit
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
+            domain: first-listener
+            enableXRatelimitHeaders: DRAFT_VERSION_03
+            rateLimitService:
+              grpcService:
+                envoyGrpc:
+                  clusterName: ratelimit_cluster
+              transportApiVersion: V3
+        - 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/ratelimit-headers-and-cidr.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.routes.yaml
new file mode 100644
index 000000000..459d975a9
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-headers-and-cidr.routes.yaml
@@ -0,0 +1,88 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - '*'
+    name: first-listener/*
+    routes:
+    - match:
+        prefix: /
+      name: first-route
+      route:
+        cluster: first-route-dest
+        rateLimits:
+        - actions:
+          - genericKey:
+              descriptorKey: first-route
+              descriptorValue: first-route
+          - headerValueMatch:
+              descriptorKey: rule-0-match-0
+              descriptorValue: rule-0-match-0
+              expectMatch: true
+              headers:
+              - name: x-user-id
+                stringMatch:
+                  exact: one
+          - maskedRemoteAddress:
+              v4PrefixMaskLen: 16
+        upgradeConfigs:
+        - upgradeType: websocket
+    - match:
+        path: example
+      name: second-route
+      route:
+        cluster: second-route-dest
+        rateLimits:
+        - actions:
+          - genericKey:
+              descriptorKey: second-route
+              descriptorValue: second-route
+          - requestHeaders:
+              descriptorKey: rule-0-match-0
+              headerName: x-user-id
+          - requestHeaders:
+              descriptorKey: rule-0-match-1
+              headerName: foobar
+          - maskedRemoteAddress:
+              v4PrefixMaskLen: 16
+        upgradeConfigs:
+        - upgradeType: websocket
+    - match:
+        prefix: /
+      name: third-route
+      route:
+        cluster: third-route-dest
+        rateLimits:
+        - actions:
+          - genericKey:
+              descriptorKey: third-route
+              descriptorValue: third-route
+          - headerValueMatch:
+              descriptorKey: rule-0-match-0
+              descriptorValue: rule-0-match-0
+              expectMatch: true
+              headers:
+              - name: x-user-id
+                stringMatch:
+                  exact: one
+          - maskedRemoteAddress:
+              v4PrefixMaskLen: 16
+        - actions:
+          - genericKey:
+              descriptorKey: third-route
+              descriptorValue: third-route
+          - headerValueMatch:
+              descriptorKey: rule-1-match-0
+              descriptorValue: rule-1-match-0
+              expectMatch: true
+              headers:
+              - name: x-user-id
+                stringMatch:
+                  exact: two
+          - requestHeaders:
+              descriptorKey: rule-1-match-1
+              headerName: foobar
+          - maskedRemoteAddress:
+              v4PrefixMaskLen: 16
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index fe361099a..c183a4f0b 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -483,6 +483,7 @@ _Appears in:_
 | `rateLimit` | _[RateLimitSpec](#ratelimitspec)_ |  false  | RateLimit allows the user to limit the number of incoming requests<br />to a predefined value based on attributes within the traffic flow. |
 | `faultInjection` | _[FaultInjection](#faultinjection)_ |  false  | FaultInjection defines the fault injection policy to be applied. This configuration can be used to<br />inject delays and abort requests to mimic failure scenarios such as service failures and overloads |
 | `useClientProtocol` | _boolean_ |  false  | UseClientProtocol configures Envoy to prefer sending requests to backends using<br />the same HTTP protocol that the incoming request used. Defaults to false, which means<br />that Envoy will use the protocol indicated by the attached BackendRef. |
+| `responseOverride` | _[ResponseOverride](#responseoverride) array_ |  false  | ResponseOverride defines the configuration to override specific responses with a custom one.<br />If multiple configurations are specified, the first one to match wins. |
 
 
 #### BasicAuth
@@ -781,7 +782,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration.<br />Default: unlimited. |
+| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration. |
 | `closeDelay` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  | CloseDelay defines the delay to use before closing connections that are rejected<br />once the limit value is reached.<br />Default: none. |
 
 
@@ -866,7 +867,7 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `contentType` | _string_ |  false  | Content Type of the response. This will be set in the Content-Type header. |
-| `body` | _[CustomResponseBody](#customresponsebody)_ |  false  | Body of the Custom Response |
+| `body` | _[CustomResponseBody](#customresponsebody)_ |  true  | Body of the Custom Response |
 
 
 #### CustomResponseBody
@@ -881,9 +882,9 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value. |
+| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value.<br />Valid values are Inline and ValueRef, default is Inline. |
 | `inline` | _string_ |  false  | Inline contains the value as an inline string. |
-| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported.<br /><br />The value of key `response.body` in the ConfigMap will be used as the response body.<br />If the key is not found, the first value in the ConfigMap will be used. |
 
 
 #### CustomResponseMatch
@@ -897,7 +898,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `statusCode` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
+| `statusCodes` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
 
 
 #### CustomTag
@@ -1026,6 +1027,7 @@ _Appears in:_
 | `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.<br /> | 
 | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.<br /> | 
 | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.<br /> | 
+| `envoy.filters.http.custom_response` | EnvoyFilterCustomResponse defines the Envoy HTTP custom response filter.<br /> | 
 | `envoy.filters.http.router` | EnvoyFilterRouter defines the Envoy HTTP router filter.<br /> | 
 
 
@@ -1479,7 +1481,7 @@ _Appears in:_
 | `extraArgs` | _string array_ |  false  | ExtraArgs defines additional command line options that are provided to Envoy.<br />More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options<br />Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. |
 | `mergeGateways` | _boolean_ |  false  | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.<br />Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.<br />This means that the port, protocol and hostname tuple must be unique for every listener.<br />If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. |
 | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ |  false  | Shutdown defines configuration for graceful envoy shutdown process. |
-| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
+| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 
 
@@ -3573,6 +3575,10 @@ ResponseValueType defines the types of values for the response body supported by
 _Appears in:_
 - [CustomResponseBody](#customresponsebody)
 
+| Value | Description |
+| ----- | ----------- |
+| `Inline` | ResponseValueTypeInline defines the "Inline" response body type.<br /> | 
+| `ValueRef` | ResponseValueTypeValueRef defines the "ValueRef" response body type.<br /> | 
 
 
 
@@ -3821,16 +3827,16 @@ _Appears in:_
 
 
 
-
+StatusCodeMatch defines the configuration for matching a status code.
 
 _Appears in:_
 - [CustomResponseMatch](#customresponsematch)
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value. |
-| `value` | _string_ |  false  | Value contains the value of the status code. |
-| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value.<br />Valid values are Value and Range, default is Value. |
+| `value` | _integer_ |  false  | Value contains the value of the status code. |
+| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | Range contains the range of status codes. |
 
 
 #### StatusCodeRange
@@ -3857,6 +3863,10 @@ StatusCodeValueType defines the types of values for the status code match suppor
 _Appears in:_
 - [StatusCodeMatch](#statuscodematch)
 
+| Value | Description |
+| ----- | ----------- |
+| `Value` | StatusCodeValueTypeValue defines the "Value" status code match type.<br /> | 
+| `Range` | StatusCodeValueTypeRange defines the "Range" status code match type.<br /> | 
 
 
 #### StringMatch
diff --git a/site/content/en/latest/install/gateway-addons-helm-api.md b/site/content/en/latest/install/gateway-addons-helm-api.md
index a0ae0ed62..9835e21cd 100644
--- a/site/content/en/latest/install/gateway-addons-helm-api.md
+++ b/site/content/en/latest/install/gateway-addons-helm-api.md
@@ -27,7 +27,7 @@ An Add-ons Helm chart for Envoy Gateway
 | https://grafana.github.io/helm-charts | grafana | 8.0.0 |
 | https://grafana.github.io/helm-charts | loki | 4.8.0 |
 | https://grafana.github.io/helm-charts | tempo | 1.3.1 |
-| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.73.1 |
+| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.108.0 |
 | https://prometheus-community.github.io/helm-charts | prometheus | 25.21.0 |
 
 ## Values
@@ -82,7 +82,7 @@ An Add-ons Helm chart for Envoy Gateway
 | loki.singleBinary.replicas | int | `1` |  |
 | loki.test.enabled | bool | `false` |  |
 | loki.write.replicas | int | `0` |  |
-| opentelemetry-collector.config.exporters.logging.verbosity | string | `"detailed"` |  |
+| opentelemetry-collector.config.exporters.debug.verbosity | string | `"detailed"` |  |
 | opentelemetry-collector.config.exporters.loki.endpoint | string | `"http://loki.monitoring.svc:3100/loki/api/v1/push"` |  |
 | opentelemetry-collector.config.exporters.otlp.endpoint | string | `"tempo.monitoring.svc:4317"` |  |
 | opentelemetry-collector.config.exporters.otlp.tls.insecure | bool | `true` |  |
@@ -91,6 +91,7 @@ An Add-ons Helm chart for Envoy Gateway
 | opentelemetry-collector.config.processors.attributes.actions[0].action | string | `"insert"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].key | string | `"loki.attribute.labels"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].value | string | `"k8s.pod.name, k8s.namespace.name"` |  |
+| opentelemetry-collector.config.receivers.datadog.endpoint | string | `"${env:MY_POD_IP}:8126"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.grpc.endpoint | string | `"${env:MY_POD_IP}:4317"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.http.endpoint | string | `"${env:MY_POD_IP}:4318"` |  |
 | opentelemetry-collector.config.receivers.zipkin.endpoint | string | `"${env:MY_POD_IP}:9411"` |  |
@@ -99,12 +100,15 @@ An Add-ons Helm chart for Envoy Gateway
 | opentelemetry-collector.config.service.pipelines.logs.processors[0] | string | `"attributes"` |  |
 | opentelemetry-collector.config.service.pipelines.logs.receivers[0] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.metrics.exporters[0] | string | `"prometheus"` |  |
-| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[1] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.traces.exporters[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"zipkin"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[2] | string | `"zipkin"` |  |
 | opentelemetry-collector.enabled | bool | `false` |  |
 | opentelemetry-collector.fullnameOverride | string | `"otel-collector"` |  |
+| opentelemetry-collector.image.repository | string | `"otel/opentelemetry-collector-contrib"` |  |
 | opentelemetry-collector.mode | string | `"deployment"` |  |
 | prometheus.alertmanager.enabled | bool | `false` |  |
 | prometheus.enabled | bool | `true` |  |
diff --git a/site/content/en/latest/tasks/extensibility/wasm.md b/site/content/en/latest/tasks/extensibility/wasm.md
index 8a640471e..baad6a580 100644
--- a/site/content/en/latest/tasks/extensibility/wasm.md
+++ b/site/content/en/latest/tasks/extensibility/wasm.md
@@ -16,7 +16,7 @@ This instantiated resource can be linked to a [Gateway][Gateway] and [HTTPRoute]
 
 ## Configuration
 
-Envoy Gateway supports two types of Wasm extensions: 
+Envoy Gateway supports two types of Wasm extensions:
 * HTTP Wasm Extension: The Wasm extension is fetched from a remote URL.
 * Image Wasm Extension: The Wasm extension is packaged as an OCI image and fetched from an image registry.
 
@@ -47,7 +47,7 @@ spec:
     code:
       type: HTTP
       http:
-        url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+        url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
         sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 EOF
 ```
@@ -73,7 +73,7 @@ spec:
     code:
       type: HTTP
       http:
-        url: https://raw.githubusercontent.com/envoyproxy/envoy/main/examples/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
+        url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm
         sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0
 ```
 
diff --git a/site/content/en/latest/tasks/observability/proxy-trace.md b/site/content/en/latest/tasks/observability/proxy-trace.md
index ddaf68e41..39243d329 100644
--- a/site/content/en/latest/tasks/observability/proxy-trace.md
+++ b/site/content/en/latest/tasks/observability/proxy-trace.md
@@ -19,7 +19,7 @@ TEMPO_IP=$(kubectl get svc tempo -n monitoring -o jsonpath='{.status.loadBalance
 
 By default, Envoy Gateway doesn't send traces to any sink.
 You can enable traces by setting the `telemetry.tracing` in the [EnvoyProxy][envoy-proxy-crd] CRD.
-Currently, Envoy Gateway support OpenTelemetry and [Zipkin](../../api/extension_types#zipkintracingprovider) tracer.
+Currently, Envoy Gateway support OpenTelemetry, [Zipkin](../../api/extension_types#zipkintracingprovider) and Datadog tracer.
 
 ### Tracing Provider
 
@@ -155,6 +155,66 @@ Verify zipkin traces from tempo:
 curl -s "http://$TEMPO_IP:3100/api/search?tags=component%3Dproxy+provider%3Dzipkin" | jq .traces
 ```
 
+{{% /tab %}}
+{{% tab header="Datadog" %}}
+
+```shell
+kubectl apply -f - <<EOF
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: eg
+spec:
+  controllerName: gateway.envoyproxy.io/gatewayclass-controller
+  parametersRef:
+    group: gateway.envoyproxy.io
+    kind: EnvoyProxy
+    name: datadog
+    namespace: envoy-gateway-system
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: datadog
+  namespace: envoy-gateway-system
+spec:
+  telemetry:
+    tracing:
+      # sample 100% of requests
+      samplingRate: 100
+      provider:
+        backendRefs:
+        - name: datadog-agent
+          namespace: monitoring
+          port: 8126
+        type: Datadog
+      customTags:
+        # This is an example of using a literal as a tag value
+        provider:
+          type: Literal
+          literal:
+            value: "datadog"
+        "k8s.pod.name":
+          type: Environment
+          environment:
+            name: ENVOY_POD_NAME
+            defaultValue: "-"
+        "k8s.namespace.name":
+          type: Environment
+          environment:
+            name: ENVOY_GATEWAY_NAMESPACE
+            defaultValue: "envoy-gateway-system"
+        # This is an example of using a header value as a tag value
+        header1:
+          type: RequestHeader
+          requestHeader:
+            name: X-Header-1
+            defaultValue: "-"
+EOF
+```
+
+Verify Datadog traces in [Datadog APM](https://docs.datadoghq.com/tracing/)
+
 {{% /tab %}}
 {{< /tabpane >}}
 
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index fe361099a..c183a4f0b 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -483,6 +483,7 @@ _Appears in:_
 | `rateLimit` | _[RateLimitSpec](#ratelimitspec)_ |  false  | RateLimit allows the user to limit the number of incoming requests<br />to a predefined value based on attributes within the traffic flow. |
 | `faultInjection` | _[FaultInjection](#faultinjection)_ |  false  | FaultInjection defines the fault injection policy to be applied. This configuration can be used to<br />inject delays and abort requests to mimic failure scenarios such as service failures and overloads |
 | `useClientProtocol` | _boolean_ |  false  | UseClientProtocol configures Envoy to prefer sending requests to backends using<br />the same HTTP protocol that the incoming request used. Defaults to false, which means<br />that Envoy will use the protocol indicated by the attached BackendRef. |
+| `responseOverride` | _[ResponseOverride](#responseoverride) array_ |  false  | ResponseOverride defines the configuration to override specific responses with a custom one.<br />If multiple configurations are specified, the first one to match wins. |
 
 
 #### BasicAuth
@@ -781,7 +782,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration.<br />Default: unlimited. |
+| `value` | _integer_ |  true  | Value of the maximum concurrent connections limit.<br />When the limit is reached, incoming connections will be closed after the CloseDelay duration. |
 | `closeDelay` | _[Duration](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Duration)_ |  false  | CloseDelay defines the delay to use before closing connections that are rejected<br />once the limit value is reached.<br />Default: none. |
 
 
@@ -866,7 +867,7 @@ _Appears in:_
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
 | `contentType` | _string_ |  false  | Content Type of the response. This will be set in the Content-Type header. |
-| `body` | _[CustomResponseBody](#customresponsebody)_ |  false  | Body of the Custom Response |
+| `body` | _[CustomResponseBody](#customresponsebody)_ |  true  | Body of the Custom Response |
 
 
 #### CustomResponseBody
@@ -881,9 +882,9 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value. |
+| `type` | _[ResponseValueType](#responsevaluetype)_ |  true  | Type is the type of method to use to read the body value.<br />Valid values are Inline and ValueRef, default is Inline. |
 | `inline` | _string_ |  false  | Inline contains the value as an inline string. |
-| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+| `valueRef` | _[LocalObjectReference](#localobjectreference)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported.<br /><br />The value of key `response.body` in the ConfigMap will be used as the response body.<br />If the key is not found, the first value in the ConfigMap will be used. |
 
 
 #### CustomResponseMatch
@@ -897,7 +898,7 @@ _Appears in:_
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `statusCode` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
+| `statusCodes` | _[StatusCodeMatch](#statuscodematch) array_ |  true  | Status code to match on. The match evaluates to true if any of the matches are successful. |
 
 
 #### CustomTag
@@ -1026,6 +1027,7 @@ _Appears in:_
 | `envoy.filters.http.rbac` | EnvoyFilterRBAC defines the Envoy RBAC filter.<br /> | 
 | `envoy.filters.http.local_ratelimit` | EnvoyFilterLocalRateLimit defines the Envoy HTTP local rate limit filter.<br /> | 
 | `envoy.filters.http.ratelimit` | EnvoyFilterRateLimit defines the Envoy HTTP rate limit filter.<br /> | 
+| `envoy.filters.http.custom_response` | EnvoyFilterCustomResponse defines the Envoy HTTP custom response filter.<br /> | 
 | `envoy.filters.http.router` | EnvoyFilterRouter defines the Envoy HTTP router filter.<br /> | 
 
 
@@ -1479,7 +1481,7 @@ _Appears in:_
 | `extraArgs` | _string array_ |  false  | ExtraArgs defines additional command line options that are provided to Envoy.<br />More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options<br />Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. |
 | `mergeGateways` | _boolean_ |  false  | MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure.<br />Setting this field to true would merge all Gateway Listeners under the parent Gateway Class.<br />This means that the port, protocol and hostname tuple must be unique for every listener.<br />If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. |
 | `shutdown` | _[ShutdownConfig](#shutdownconfig)_ |  false  | Shutdown defines configuration for graceful envoy shutdown process. |
-| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
+| `filterOrder` | _[FilterPosition](#filterposition) array_ |  false  | FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain.<br />The FilterPosition in the list will be applied in the order they are defined.<br />If unspecified, the default filter order is applied.<br />Default filter order is:<br /><br />- envoy.filters.http.health_check<br /><br />- envoy.filters.http.fault<br /><br />- envoy.filters.http.cors<br /><br />- envoy.filters.http.ext_authz<br /><br />- envoy.filters.http.basic_auth<br /><br />- envoy.filters.http.oauth2<br /><br />- envoy.filters.http.jwt_authn<br /><br />- envoy.filters.http.stateful_session<br /><br />- envoy.filters.http.ext_proc<br /><br />- envoy.filters.http.wasm<br /><br />- envoy.filters.http.rbac<br /><br />- envoy.filters.http.local_ratelimit<br /><br />- envoy.filters.http.ratelimit<br /><br />- envoy.filters.http.custom_response<br /><br />- envoy.filters.http.router<br /><br />Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. |
 | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ |  false  | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.<br />These settings are applied on backends for which TLS policies are specified. |
 
 
@@ -3573,6 +3575,10 @@ ResponseValueType defines the types of values for the response body supported by
 _Appears in:_
 - [CustomResponseBody](#customresponsebody)
 
+| Value | Description |
+| ----- | ----------- |
+| `Inline` | ResponseValueTypeInline defines the "Inline" response body type.<br /> | 
+| `ValueRef` | ResponseValueTypeValueRef defines the "ValueRef" response body type.<br /> | 
 
 
 
@@ -3821,16 +3827,16 @@ _Appears in:_
 
 
 
-
+StatusCodeMatch defines the configuration for matching a status code.
 
 _Appears in:_
 - [CustomResponseMatch](#customresponsematch)
 
 | Field | Type | Required | Description |
 | ---   | ---  | ---      | ---         |
-| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value. |
-| `value` | _string_ |  false  | Value contains the value of the status code. |
-| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | ValueRef contains the contents of the body<br />specified as a local object reference.<br />Only a reference to ConfigMap is supported. |
+| `type` | _[StatusCodeValueType](#statuscodevaluetype)_ |  true  | Type is the type of value.<br />Valid values are Value and Range, default is Value. |
+| `value` | _integer_ |  false  | Value contains the value of the status code. |
+| `range` | _[StatusCodeRange](#statuscoderange)_ |  false  | Range contains the range of status codes. |
 
 
 #### StatusCodeRange
@@ -3857,6 +3863,10 @@ StatusCodeValueType defines the types of values for the status code match suppor
 _Appears in:_
 - [StatusCodeMatch](#statuscodematch)
 
+| Value | Description |
+| ----- | ----------- |
+| `Value` | StatusCodeValueTypeValue defines the "Value" status code match type.<br /> | 
+| `Range` | StatusCodeValueTypeRange defines the "Range" status code match type.<br /> | 
 
 
 #### StringMatch
diff --git a/site/content/zh/latest/install/gateway-addons-helm-api.md b/site/content/zh/latest/install/gateway-addons-helm-api.md
index a0ae0ed62..9835e21cd 100644
--- a/site/content/zh/latest/install/gateway-addons-helm-api.md
+++ b/site/content/zh/latest/install/gateway-addons-helm-api.md
@@ -27,7 +27,7 @@ An Add-ons Helm chart for Envoy Gateway
 | https://grafana.github.io/helm-charts | grafana | 8.0.0 |
 | https://grafana.github.io/helm-charts | loki | 4.8.0 |
 | https://grafana.github.io/helm-charts | tempo | 1.3.1 |
-| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.73.1 |
+| https://open-telemetry.github.io/opentelemetry-helm-charts | opentelemetry-collector | 0.108.0 |
 | https://prometheus-community.github.io/helm-charts | prometheus | 25.21.0 |
 
 ## Values
@@ -82,7 +82,7 @@ An Add-ons Helm chart for Envoy Gateway
 | loki.singleBinary.replicas | int | `1` |  |
 | loki.test.enabled | bool | `false` |  |
 | loki.write.replicas | int | `0` |  |
-| opentelemetry-collector.config.exporters.logging.verbosity | string | `"detailed"` |  |
+| opentelemetry-collector.config.exporters.debug.verbosity | string | `"detailed"` |  |
 | opentelemetry-collector.config.exporters.loki.endpoint | string | `"http://loki.monitoring.svc:3100/loki/api/v1/push"` |  |
 | opentelemetry-collector.config.exporters.otlp.endpoint | string | `"tempo.monitoring.svc:4317"` |  |
 | opentelemetry-collector.config.exporters.otlp.tls.insecure | bool | `true` |  |
@@ -91,6 +91,7 @@ An Add-ons Helm chart for Envoy Gateway
 | opentelemetry-collector.config.processors.attributes.actions[0].action | string | `"insert"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].key | string | `"loki.attribute.labels"` |  |
 | opentelemetry-collector.config.processors.attributes.actions[0].value | string | `"k8s.pod.name, k8s.namespace.name"` |  |
+| opentelemetry-collector.config.receivers.datadog.endpoint | string | `"${env:MY_POD_IP}:8126"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.grpc.endpoint | string | `"${env:MY_POD_IP}:4317"` |  |
 | opentelemetry-collector.config.receivers.otlp.protocols.http.endpoint | string | `"${env:MY_POD_IP}:4318"` |  |
 | opentelemetry-collector.config.receivers.zipkin.endpoint | string | `"${env:MY_POD_IP}:9411"` |  |
@@ -99,12 +100,15 @@ An Add-ons Helm chart for Envoy Gateway
 | opentelemetry-collector.config.service.pipelines.logs.processors[0] | string | `"attributes"` |  |
 | opentelemetry-collector.config.service.pipelines.logs.receivers[0] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.metrics.exporters[0] | string | `"prometheus"` |  |
-| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.metrics.receivers[1] | string | `"otlp"` |  |
 | opentelemetry-collector.config.service.pipelines.traces.exporters[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"otlp"` |  |
-| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"zipkin"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[0] | string | `"datadog"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[1] | string | `"otlp"` |  |
+| opentelemetry-collector.config.service.pipelines.traces.receivers[2] | string | `"zipkin"` |  |
 | opentelemetry-collector.enabled | bool | `false` |  |
 | opentelemetry-collector.fullnameOverride | string | `"otel-collector"` |  |
+| opentelemetry-collector.image.repository | string | `"otel/opentelemetry-collector-contrib"` |  |
 | opentelemetry-collector.mode | string | `"deployment"` |  |
 | prometheus.alertmanager.enabled | bool | `false` |  |
 | prometheus.enabled | bool | `true` |  |
diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go
index 49f033eb6..d5e6a1b2d 100644
--- a/test/cel-validation/backendtrafficpolicy_test.go
+++ b/test/cel-validation/backendtrafficpolicy_test.go
@@ -1257,6 +1257,251 @@ func TestBackendTrafficPolicyTarget(t *testing.T) {
 			},
 			wantErrors: []string{},
 		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Type: ptr.To(egv1a1.StatusCodeValueTypeValue),
+										Range: &egv1a1.StatusCodeRange{
+											Start: 100,
+											End:   200,
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"value must be set for type Value",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Range: &egv1a1.StatusCodeRange{
+											Start: 100,
+											End:   200,
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"value must be set for type Value",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Type:  ptr.To(egv1a1.StatusCodeValueTypeRange),
+										Value: ptr.To(100),
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"range must be set for type Range",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Type: ptr.To(egv1a1.StatusCodeValueTypeRange),
+										Range: &egv1a1.StatusCodeRange{
+											Start: 200,
+											End:   100,
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"end must be greater than start",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Value: ptr.To(100),
+									},
+								},
+							},
+							Response: egv1a1.CustomResponse{
+								Body: egv1a1.CustomResponseBody{
+									ValueRef: &gwapiv1a2.LocalObjectReference{
+										Kind: gwapiv1a2.Kind("ConfigMap"),
+										Name: gwapiv1a2.ObjectName("eg"),
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"inline must be set for type Inline",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Value: ptr.To(100),
+									},
+								},
+							},
+							Response: egv1a1.CustomResponse{
+								Body: egv1a1.CustomResponseBody{
+									Type:   ptr.To(egv1a1.ResponseValueTypeValueRef),
+									Inline: ptr.To("foo"),
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"valueRef must be set for type ValueRef",
+			},
+		},
+		{
+			desc: "both targetref and targetrefs specified",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					ResponseOverride: []*egv1a1.ResponseOverride{
+						{
+							Match: egv1a1.CustomResponseMatch{
+								StatusCodes: []egv1a1.StatusCodeMatch{
+									{
+										Value: ptr.To(100),
+									},
+								},
+							},
+							Response: egv1a1.CustomResponse{
+								Body: egv1a1.CustomResponseBody{
+									Type: ptr.To(egv1a1.ResponseValueTypeValueRef),
+									ValueRef: &gwapiv1a2.LocalObjectReference{
+										Kind: gwapiv1a2.Kind("Foo"),
+										Name: gwapiv1a2.ObjectName("eg"),
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"only ConfigMap is supported for ValueRe",
+			},
+		},
 	}
 
 	for _, tc := range cases {
diff --git a/test/cel-validation/clienttrafficpolicy_test.go b/test/cel-validation/clienttrafficpolicy_test.go
index 14dd915d7..3558d1848 100644
--- a/test/cel-validation/clienttrafficpolicy_test.go
+++ b/test/cel-validation/clienttrafficpolicy_test.go
@@ -314,6 +314,52 @@ func TestClientTrafficPolicyTarget(t *testing.T) {
 				"spec.connection.bufferLimit: Invalid value: \"15m\": spec.connection.bufferLimit in body should match '^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$', <nil>: Invalid value: \"\"",
 			},
 		},
+		{
+			desc: "invalid Connection Limit Empty",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					Connection: &egv1a1.ClientConnection{
+						ConnectionLimit: &egv1a1.ConnectionLimit{},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.connection.connectionLimit.value: Invalid value: 0: spec.connection.connectionLimit.value in body should be greater than or equal to 1",
+			},
+		},
+		{
+			desc: "invalid Connection Limit < 1",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+						TargetRef: &gwapiv1a2.LocalPolicyTargetReferenceWithSectionName{
+							LocalPolicyTargetReference: gwapiv1a2.LocalPolicyTargetReference{
+								Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+								Kind:  gwapiv1a2.Kind("Gateway"),
+								Name:  gwapiv1a2.ObjectName("eg"),
+							},
+						},
+					},
+					Connection: &egv1a1.ClientConnection{
+						ConnectionLimit: &egv1a1.ConnectionLimit{
+							Value: -1, // Value: 0 is covered by existence test, as 0 is the nil value.
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.connection.connectionLimit.value: Invalid value: -1: spec.connection.connectionLimit.value in body should be greater than or equal to 1",
+			},
+		},
 		{
 			desc: "invalid InitialStreamWindowSize format",
 			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
diff --git a/test/e2e/testdata/ratelimit-headers-and-cidr-match.yaml b/test/e2e/testdata/ratelimit-headers-and-cidr-match.yaml
new file mode 100644
index 000000000..fef2f645a
--- /dev/null
+++ b/test/e2e/testdata/ratelimit-headers-and-cidr-match.yaml
@@ -0,0 +1,45 @@
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: ratelimit-headers-and-cidr
+  namespace: gateway-conformance-infra
+spec:
+  targetRefs:
+    - group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: header-and-cidr-ratelimit
+  rateLimit:
+    type: Global
+    global:
+      rules:
+        - clientSelectors:
+            - headers:
+                - name: x-user-id
+                  type: Exact
+                  value: one
+                - name: x-user-org
+                  type: Exact
+                  value: acme
+              sourceCIDR:
+                value: 0.0.0.0/0
+                type: Distinct
+          limit:
+            requests: 3
+            unit: Hour
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: header-and-cidr-ratelimit
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /get
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
diff --git a/test/e2e/testdata/response-override.yaml b/test/e2e/testdata/response-override.yaml
new file mode 100644
index 000000000..084747aaa
--- /dev/null
+++ b/test/e2e/testdata/response-override.yaml
@@ -0,0 +1,62 @@
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: response-override
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: same-namespace
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /
+      backendRefs:
+        - name: infra-backend-v1
+          port: 8080
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: response-override-config
+  namespace: gateway-conformance-infra
+data:
+  response.body: '{"error": "Internal Server Error"}'
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: response-override
+  namespace: gateway-conformance-infra
+spec:
+  targetRef:
+    group: gateway.networking.k8s.io
+    kind: HTTPRoute
+    name: response-override
+  responseOverride:
+    - match:
+        statusCodes:
+          - type: Value
+            value: 404
+      response:
+        contentType: text/plain
+        body:
+          type: Inline
+          inline: "Oops! Your request is not found."
+    - match:
+        statusCodes:
+          - type: Value
+            value: 500
+          - type: Range
+            range:
+              start: 501
+              end: 511
+      response:
+        contentType: application/json
+        body:
+          type: ValueRef
+          valueRef:
+            group: ""
+            kind: ConfigMap
+            name: response-override-config
diff --git a/test/e2e/testdata/tracing-datadog.yaml b/test/e2e/testdata/tracing-datadog.yaml
new file mode 100644
index 000000000..e4f54a7ee
--- /dev/null
+++ b/test/e2e/testdata/tracing-datadog.yaml
@@ -0,0 +1,91 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: eg-special-case-datadog
+  namespace: gateway-conformance-infra
+spec:
+  gatewayClassName: "{GATEWAY_CLASS_NAME}"
+  listeners:
+    - name: http
+      port: 80
+      protocol: HTTP
+      allowedRoutes:
+        namespaces:
+          from: All
+  infrastructure:
+    parametersRef:
+      group: gateway.envoyproxy.io
+      kind: EnvoyProxy
+      name: datadog-tracing
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: datadog-agent
+  namespace: monitoring
+spec:
+  selector:
+    app.kubernetes.io/instance: eg-addons
+    app.kubernetes.io/name: opentelemetry-collector
+    component: standalone-collector
+  ports:
+    - protocol: TCP
+      port: 8126
+      targetPort: 8126
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: datadog-tracing
+  namespace: gateway-conformance-infra
+spec:
+  logging:
+    level:
+      default: debug
+  telemetry:
+    tracing:
+      provider:
+        type: Datadog
+        backendRefs:
+          - name: datadog-agent
+            namespace: monitoring
+            port: 8126
+      customTags:
+        "provider":
+          type: Literal
+          literal:
+            value: "datadog"
+        "k8s.cluster.name":
+          type: Literal
+          literal:
+            value: "envoy-gateway"
+        "k8s.pod.name":
+          type: Environment
+          environment:
+            name: ENVOY_POD_NAME
+            defaultValue: "-"
+        "k8s.namespace.name":
+          type: Environment
+          environment:
+            name: ENVOY_GATEWAY_NAMESPACE
+            defaultValue: "envoy-gateway-system"
+  shutdown:
+    drainTimeout: 5s
+    minDrainDuration: 1s
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: tracing-datadog
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: eg-special-case-datadog
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /datadog
+      backendRefs:
+        - name: infra-backend-v2
+          port: 8080
diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go
index b87576b60..f0082d77e 100644
--- a/test/e2e/tests/ratelimit.go
+++ b/test/e2e/tests/ratelimit.go
@@ -30,6 +30,7 @@ func init() {
 	ConformanceTests = append(ConformanceTests, RateLimitHeadersDisabled)
 	ConformanceTests = append(ConformanceTests, RateLimitBasedJwtClaimsTest)
 	ConformanceTests = append(ConformanceTests, RateLimitMultipleListenersTest)
+	ConformanceTests = append(ConformanceTests, RateLimitHeadersAndCIDRMatchTest)
 }
 
 var RateLimitCIDRMatchTest = suite.ConformanceTest{
@@ -538,6 +539,118 @@ var RateLimitMultipleListenersTest = suite.ConformanceTest{
 	},
 }
 
+var RateLimitHeadersAndCIDRMatchTest = suite.ConformanceTest{
+	ShortName:   "RateLimitHeadersAndCIDRMatch",
+	Description: "Limit requests on rule that has both headers and cidr matches",
+	Manifests:   []string{"testdata/ratelimit-headers-and-cidr-match.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "header-and-cidr-ratelimit", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+		t.Run("all matched both headers and cidr can got limited", func(t *testing.T) {
+			requestHeaders := map[string]string{
+				"x-user-id":  "one",
+				"x-user-org": "acme",
+			}
+
+			ratelimitHeader := make(map[string]string)
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path:    "/get",
+					Headers: requestHeaders,
+				},
+				Response: http.Response{
+					StatusCode: 200,
+					Headers:    ratelimitHeader,
+				},
+				Namespace: ns,
+			}
+			expectOkResp.Response.Headers["X-Ratelimit-Limit"] = "3, 3;w=3600"
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			expectLimitResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path:    "/get",
+					Headers: requestHeaders,
+				},
+				Response: http.Response{
+					StatusCode: 429,
+				},
+				Namespace: ns,
+			}
+			expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")
+
+			// should just send exactly 4 requests, and expect 429
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected response for the first three requests: %v", err)
+			}
+			if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
+				t.Errorf("failed to get expected response for the last (fourth) request: %v", err)
+			}
+		})
+
+		t.Run("only partly matched headers cannot got limited", func(t *testing.T) {
+			requestHeaders := map[string]string{
+				"x-user-id": "one",
+			}
+
+			// it does not require any rate limit header, since this request never be rate limited.
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path:    "/get",
+					Headers: requestHeaders,
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			// send exactly 4 requests, and still expect 200
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected responses for the request: %v", err)
+			}
+		})
+
+		t.Run("only matched cidr cannot got limited", func(t *testing.T) {
+			// it does not require any rate limit header, since this request never be rate limited.
+			expectOkResp := http.ExpectedResponse{
+				Request: http.Request{
+					Path: "/get",
+				},
+				Response: http.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
+
+			// send exactly 4 requests, and still expect 200
+
+			// keep sending requests till get 200 first, that will cost one 200
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)
+
+			// fire the rest of the requests
+			if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
+				t.Errorf("failed to get expected responses for the request: %v", err)
+			}
+		})
+	},
+}
+
 func GotExactExpectedResponse(t *testing.T, n int, r roundtripper.RoundTripper, req roundtripper.Request, resp http.ExpectedResponse) error {
 	for i := 0; i < n; i++ {
 		cReq, cRes, err := r.CaptureRoundTrip(req)
diff --git a/test/e2e/tests/response-override.go b/test/e2e/tests/response-override.go
new file mode 100644
index 000000000..b21db88e2
--- /dev/null
+++ b/test/e2e/tests/response-override.go
@@ -0,0 +1,83 @@
+// 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
+
+package tests
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	httputils "sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+
+	"github.com/envoyproxy/gateway/internal/gatewayapi"
+	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, ResponseOverrideTest)
+}
+
+var ResponseOverrideTest = suite.ConformanceTest{
+	ShortName:   "ResponseOverrideSpecificUser",
+	Description: "Response Override",
+	Manifests:   []string{"testdata/response-override.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("response override", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "response-override", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+			ancestorRef := gwapiv1a2.ParentReference{
+				Group:     gatewayapi.GroupPtr(gwapiv1.GroupName),
+				Kind:      gatewayapi.KindPtr(resource.KindGateway),
+				Namespace: gatewayapi.NamespacePtr(gwNN.Namespace),
+				Name:      gwapiv1.ObjectName(gwNN.Name),
+			}
+			BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "response-override", Namespace: ns}, suite.ControllerName, ancestorRef)
+			verifyResponseOverride(t, gwAddr, 404, "text/plain", "Oops! Your request is not found.")
+			verifyResponseOverride(t, gwAddr, 500, "application/json", `{"error": "Internal Server Error"}`)
+		})
+	},
+}
+
+func verifyResponseOverride(t *testing.T, gwAddr string, statusCode int, expectedContentType string, expectedBody string) {
+	reqURL := url.URL{
+		Scheme: "http",
+		Host:   httputils.CalculateHost(t, gwAddr, "http"),
+		Path:   fmt.Sprintf("/status/%d", statusCode),
+	}
+
+	rsp, err := http.Get(reqURL.String())
+	if err != nil {
+		t.Fatalf("failed to get response: %v", err)
+	}
+
+	// Verify that the response body is overridden
+	defer rsp.Body.Close()
+	body, err := io.ReadAll(rsp.Body)
+	if err != nil {
+		t.Fatalf("failed to read response body: %v", err)
+	}
+	if string(body) != expectedBody {
+		t.Errorf("expected response body to be %s but got %s", expectedBody, string(body))
+	}
+
+	// Verify that the content type is overridden
+	contentType := rsp.Header.Get("Content-Type")
+	if contentType != expectedContentType {
+		t.Errorf("expected content type to be %s but got %s", expectedContentType, contentType)
+	}
+}
diff --git a/test/e2e/tests/tracing.go b/test/e2e/tests/tracing.go
index 5ead14b48..93e4cb238 100644
--- a/test/e2e/tests/tracing.go
+++ b/test/e2e/tests/tracing.go
@@ -24,7 +24,7 @@ import (
 )
 
 func init() {
-	ConformanceTests = append(ConformanceTests, OpenTelemetryTracingTest, ZipkinTracingTest)
+	ConformanceTests = append(ConformanceTests, OpenTelemetryTracingTest, ZipkinTracingTest, DatadogTracingTest)
 }
 
 var OpenTelemetryTracingTest = suite.ConformanceTest{
@@ -141,3 +141,68 @@ var ZipkinTracingTest = suite.ConformanceTest{
 		})
 	},
 }
+
+var DatadogTracingTest = suite.ConformanceTest{
+	ShortName:   "DatadogTracing",
+	Description: "Make sure Datadog tracing is working",
+	Manifests:   []string{"testdata/tracing-datadog.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		t.Run("tempo", func(t *testing.T) {
+			ns := "gateway-conformance-infra"
+			routeNN := types.NamespacedName{Name: "tracing-datadog", Namespace: ns}
+			gwNN := types.NamespacedName{Name: "eg-special-case-datadog", Namespace: ns}
+			gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+			expectedResponse := httputils.ExpectedResponse{
+				Request: httputils.Request{
+					Path: "/datadog",
+				},
+				Response: httputils.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			// make sure listener is ready
+			httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+			tags := map[string]string{
+				"component":    "proxy",
+				"provider":     "datadog",
+				"service.name": fmt.Sprintf("%s.%s", gwNN.Name, gwNN.Namespace),
+			}
+			if err := wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Minute, true,
+				func(ctx context.Context) (bool, error) {
+					preCount, err := QueryTraceFromTempo(t, suite.Client, tags)
+					if err != nil {
+						tlog.Logf(t, "failed to get trace count from tempo: %v", err)
+						return false, nil
+					}
+
+					httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+					// looks like we need almost 15 seconds to get the trace from Tempo?
+					err = wait.PollUntilContextTimeout(context.TODO(), time.Second, 60*time.Second, true, func(ctx context.Context) (done bool, err error) {
+						curCount, err := QueryTraceFromTempo(t, suite.Client, tags)
+						if err != nil {
+							tlog.Logf(t, "failed to get curCount count from tempo: %v", err)
+							return false, nil
+						}
+
+						if curCount > preCount {
+							return true, nil
+						}
+
+						return false, nil
+					})
+					if err != nil {
+						tlog.Logf(t, "failed to get current count from tempo: %v", err)
+						return false, nil
+					}
+
+					return true, nil
+				}); err != nil {
+				t.Errorf("failed to get trace from tempo: %v", err)
+			}
+		})
+	},
+}
diff --git a/test/helm/gateway-addons-helm/e2e.out.yaml b/test/helm/gateway-addons-helm/e2e.out.yaml
index 15445239f..1e7c8fda8 100644
--- a/test/helm/gateway-addons-helm/e2e.out.yaml
+++ b/test/helm/gateway-addons-helm/e2e.out.yaml
@@ -32,10 +32,10 @@ metadata:
   name: otel-collector
   namespace: monitoring
   labels:
-    helm.sh/chart: opentelemetry-collector-0.73.1
+    helm.sh/chart: opentelemetry-collector-0.108.0
     app.kubernetes.io/name: opentelemetry-collector
     app.kubernetes.io/instance: gateway-addons-helm
-    app.kubernetes.io/version: "0.88.0"
+    app.kubernetes.io/version: "0.111.0"
     app.kubernetes.io/managed-by: Helm
 ---
 # Source: gateway-addons-helm/charts/prometheus/templates/serviceaccount.yaml
@@ -219,16 +219,16 @@ metadata:
   name: otel-collector
   namespace: monitoring
   labels:
-    helm.sh/chart: opentelemetry-collector-0.73.1
+    helm.sh/chart: opentelemetry-collector-0.108.0
     app.kubernetes.io/name: opentelemetry-collector
     app.kubernetes.io/instance: gateway-addons-helm
-    app.kubernetes.io/version: "0.88.0"
+    app.kubernetes.io/version: "0.111.0"
     app.kubernetes.io/managed-by: Helm
+    
 data:
   relay: |
     exporters:
-      debug: {}
-      logging:
+      debug:
         verbosity: detailed
       loki:
         endpoint: http://loki.monitoring.svc:3100/loki/api/v1/push
@@ -239,9 +239,8 @@ data:
       prometheus:
         endpoint: 0.0.0.0:19001
     extensions:
-      health_check: {}
-      memory_ballast:
-        size_in_percentage: 40
+      health_check:
+        endpoint: ${env:MY_POD_IP}:13133
     processors:
       attributes:
         actions:
@@ -254,6 +253,8 @@ data:
         limit_percentage: 80
         spike_limit_percentage: 25
     receivers:
+      datadog:
+        endpoint: ${env:MY_POD_IP}:8126
       jaeger:
         protocols:
           grpc:
@@ -296,6 +297,7 @@ data:
           - memory_limiter
           - batch
           receivers:
+          - datadog
           - otlp
         traces:
           exporters:
@@ -304,6 +306,7 @@ data:
           - memory_limiter
           - batch
           receivers:
+          - datadog
           - otlp
           - zipkin
       telemetry:
@@ -9517,11 +9520,12 @@ metadata:
   name: otel-collector
   namespace: monitoring
   labels:
-    helm.sh/chart: opentelemetry-collector-0.73.1
+    helm.sh/chart: opentelemetry-collector-0.108.0
     app.kubernetes.io/name: opentelemetry-collector
     app.kubernetes.io/instance: gateway-addons-helm
-    app.kubernetes.io/version: "0.88.0"
+    app.kubernetes.io/version: "0.111.0"
     app.kubernetes.io/managed-by: Helm
+    
     component: standalone-collector
 spec:
   type: ClusterIP
@@ -9733,11 +9737,12 @@ metadata:
   name: otel-collector
   namespace: monitoring
   labels:
-    helm.sh/chart: opentelemetry-collector-0.73.1
+    helm.sh/chart: opentelemetry-collector-0.108.0
     app.kubernetes.io/name: opentelemetry-collector
     app.kubernetes.io/instance: gateway-addons-helm
-    app.kubernetes.io/version: "0.88.0"
+    app.kubernetes.io/version: "0.111.0"
     app.kubernetes.io/managed-by: Helm
+    
 spec:
   replicas: 1
   revisionHistoryLimit: 10
@@ -9751,7 +9756,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/config: 4eb06aca6ff4da4de927cb9ba7d8ceb883d2484011fbd670683037b8ea4d996c
+        checksum/config: 270a8503091b51a264317115cf6df46b4501b03fc135eca95b93dca57a522a70
         
       labels:
         app.kubernetes.io/name: opentelemetry-collector
@@ -9765,12 +9770,11 @@ spec:
         {}
       containers:
         - name: opentelemetry-collector
-          command:
-            - /otelcol-contrib
+          args:
             - --config=/conf/relay.yaml
           securityContext:
             {}
-          image: "otel/opentelemetry-collector-contrib:0.88.0"
+          image: "otel/opentelemetry-collector-contrib:0.111.0"
           imagePullPolicy: IfNotPresent
           ports: