- Author(s): markdroth
- Approver: ejona86, dfawley
- Status: {Draft, In Review, Ready for Implementation, Implemented}
- Implemented in: <language, ...>
- Last updated: 2026-03-02
- Discussion at: https://groups.google.com/g/grpc-io/c/es5taH0OZS8
gRPC will support the xDS Composite filter, which is a "wrapper" filter that dynamically determines which filter to use based on request attributes.
xDS support in the gRPC client and server are described in A27 and A36, respectively. xDS HTTP filter support is described in A39.
The composite filter will make use of the Unified Matching API and CEL support described in A106.
- A27: xDS-Based Global Load Balancing
- A39: xDS HTTP Filter Support
- A36: xDS-Enabled Servers
- A83: xDS GCP Authentication Filter
- A106: xDS Unified Matcher and CEL Integration (pending)
We will support the composite filter in both the gRPC client and gRPC server.
Today, the composite filter supports configuring only one filter as a result of the matching tree. However, we have use-cases where we need to select a chain of more than one filter based on the matching tree. As a result, we are proposing a change to the composite filter's config to allow selecting a chain of filters (envoyproxy/envoy#40885).
The composite filter is configured via the
envoy.extensions.common.matching.v3.ExtensionWithMatcher
proto
message. Within it, gRPC will look at the following fields:
- extension_config:
This must contain a
envoy.extensions.filters.http.composite.v3.Compositeproto message, which has no fields. - xds_matcher:
Specifies a unified matcher tree indicating the config of the filter
to use, as described in A106. May be unset, in which case the
filter is a no-op. If set, validation will fail if
keep_matchingis enabled anywhere in the matcher tree. The actions in this tree must be one of two types:envoy.extensions.filters.common.matcher.action.v3.SkipFilter: This indicates that no filter will be executed.envoy.extensions.filters.http.composite.v3.ExecuteFilterAction: This indicates which filter(s) should be executed. Within it:- typed_config:
The filter to configure. See Nested Filter
Validation below for validation rules.
This field is ignored if
filter_chainis set. It is an error if neithertyped_confignorfilter_chainare set. - dynamic_config: This field will be ignored for now, since gRPC does not currently support ECDS. Support for ECDS will be added in a subsequent gRFC.
- filter_chain (new field added in
envoyproxy/envoy#40885): This specifies a
chain of filters to call, in order. See Nested Filter
Validation below for validation rules.
If set, the
typed_configfield is ignored. It is an error if neithertyped_confignorfilter_chainare set. - sample_percent:
- Optional; if unset, the specified filter(s) are always executed.
If set, for each RPC, a random number will be generated between
0 and 100, and if that number is less than the specified
threshold, the specified filter(s) will be executed.
Within this field:
- default_value: This field must be present. The configured value will be capped at 100%.
- runtime_key: This field will be ignored, since gRPC does not have a runtime system.
- Optional; if unset, the specified filter(s) are always executed.
If set, for each RPC, a random number will be generated between
0 and 100, and if that number is less than the specified
threshold, the specified filter(s) will be executed.
Within this field:
- typed_config:
The filter to configure. See Nested Filter
Validation below for validation rules.
This field is ignored if
- matcher: gRPC will not support this deprecated field.
We will also support per-route overrides via the
envoy.extensions.common.matching.v3.ExtensionWithMatcherPerRoute
proto.
In this proto, the
xds_matcher
field must be validated the same way as the corresponding field in the
top-level config. The value of this field will replace the value of the
field in the top-level config.
The parsed representation of the composite filter's config will be
a matcher tree, using the unified matcher API described in A106.
The actions in the matcher tree will be one of two possible values: a
list of parsed filter configs, or an indication that the filter should
be skipped (in the case of a SkipFilter proto).
Note that in order to avoid potential stack overflows, we will impose a maximum recursion depth of 8 when parsing HTTP filter configs.
Filter configs within the matcher tree will be validated using the xDS HTTP filter registry, just as the top-level filters in the HTTP connection manager config are.
It will be considered a configuration error if a nested filter is a terminal filter (as described in A39). While it is conceivably possible that there could be a use-case where the composite filter is intended to provide a terminal filter, it will be difficult to determine at config validation time whether this will actually happen in all cases, so for now we will simply disallow this.
Note that, as per A39, any given xDS HTTP filter may be supported on only the gRPC client or server side. When processing the filter list in LDS, we know whether the resource is an API listener (client side) or socket listener (server side), so we reject filters that are not supported on the side they are being configured on. However, because the composite filter's per-route override config may be delivered via RDS instead of LDS, it will in this case not be possible for gRPC to detect that a filter is being configured on an unsupported side when validating the RDS resource. Instead, the composite filter will need to handle this on a per-RPC basis: if a nested filter chain includes a filter that is not supported on the side that it is running on, we will fail the RPC with status UNAVAILABLE. Note that if the problematic filter is not the first filter in the nested filter chain, implementations may fail the RPC without ever starting to process the RPC on that filter chain, or they may wait to fail the RPC until it gets to the problematic filter.
In addition to the CEL request attributes described in A106, we will also add support for some additional CEL attributes that we expect to be useful for the composite filter.
On both the gRPC client and server sides, we will add support for the
xds.cluster_metadata.filter_metadata attribute. This will be backed
by the same cluster metadata whose parsing was added in A83. Note that
we will support only xds.cluster_metadata.filter_metadata, not
xds.cluster_metadata.typed_filter_metadata, so that we do not have to
handle protobuf descriptor functionality; to that end, we will use only
those entries in the parsed metadata map that correspond to
google.protobuf.Struct type. The actual CEL expression we see will be
something like xds.cluster_metadata.filter_metadata['%s']['%s'], where
the first %s indicates the key of the entry in the parsed metadata map
and the second %s indicates the key in the JSON object. If the JSON
value is not an object, this will not resolve. The type of the CEL
value will be based on the type of the underlying JSON value, either
string or integer.
We will also add support for the following attributes on the gRPC server side only (these attributes are not relevant on the client side):
source.addresssource.portconnection.requested_server_nameconnection.tls_versionconnection.sha256_peer_certificate_digest
When the filter sees the client's initial metadata, it will evaluate the matcher tree for the RPC. The result of that evaluation will be one of the following:
- If the matcher tree does not find a match, the RPC will be failed with UNAVAILABLE status.
- If the matcher tree finds a
SkipFiltermatch, the filter will simply pass the RPC through to the next filter (the one after the composite filter), without delegating to any nested filters. - If the matcher tree finds an
ExecuteFilterActionmatch, then the filter will generate a random number and check the sample_percent field to determine if the RPC should be sampled. If the RPC is not sampled, then the filter will pass the RPC through to the next filter (the one after the composite filter), without delegating to any nested filters. Otherwise (if the RPC is sampled), the RPC will be passed to the nested filter chain before being sent to the next filter (the one after the composite filter).
Note: Because all of the CEL attributes that we are currently supporting are available when we see the client's initial metadata, that is the point at which the filter will evaluate the matcher tree and decide which filter chain to use. After that point, all other filter hooks will be delegated to the chosen filter chain. (If we ever in the future need to support other attributes that are not yet available at this point, such as response attributes, we might need a more complex structure here.)
Support for the composite filter will be guarded by the
GRPC_EXPERIMENTAL_XDS_COMPOSITE_FILTER environment variable. This
guard will be removed once the feature passes interop tests.
The composite filter API seems a little unusual. One would naively
have expected it to be structured by having the matcher tree live
in the config for the composite filter directly, rather than using
ExtensionWithMatcher. Unfortunately, that's not the way the API evolved
in Envoy, so we'll stick with what already exists for compatibility
reasons. (Envoy is currently attempting to add a more sane API in
envoyproxy/envoy#43227. In the future,
we can support that new API as well.)
Will be implemented in C-core, Java, Go, and Node.