This episode opens Envoy's extension model: custom filter and WASM architecture, how to write a simple WASM filter with proxy-wasm, and use cases for custom auth, telemetry enrichment, and request transformation.

Envoy's built-in filters are complete, but sometimes you need behavior that isn't available. Episode 16 opens Envoy's extension model: two paths for adding capabilities — native C++ filters and WASM filters that can be compiled from other languages like Rust, Go, or C++.
You'll learn how the extension architecture works, write a simple WASM filter, and see real use cases: custom auth, telemetry enrichment, and request transformation. This is the door to an Envoy you truly control.
Envoy supports two main ways to add functionality:
The WASM model is based on the Proxy-Wasm standard (the proxy-wasm ABI), used not only by Envoy but also by other proxy ecosystems. With this ABI, one WASM binary can run on many proxies.
curl -s localhost:9901/extensions | python3 -m json.tool | head -30The endpoint local:9901/extensions lists all extensions compiled into the Envoy binary. Note the HTTP, network, and transport socket filter names — this is the inventory of your Envoy's capabilities.
The most comfortable way to write WASM today is Rust with the proxy-wasm SDK. Set up the toolchain:
rustup target add wasm32-wasip1
cargo new --lib my-envoy-filterThe wasm32-wasip1 target is the WASM compile target supported by proxy-wasm. This target name is the latest convention; on older SDK versions you may see wasm32-unknown-unknown.
Here's a filter that adds a header to every response:
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
#[derive(Default)]
struct HeaderInjection;
impl Context for HeaderInjection {}
impl HttpContext for HeaderInjection {
fn on_http_response_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
self.set_http_response_header("x-wasm-filter", Some("active"));
Action::Continue
}
}
proxy_wasm::main! { HeaderInjection }The on_http_response_headers function is called when response headers arrive, and set_http_response_header adds the x-wasm-filter header. This is a minimal example proving a WASM filter actually runs.
cargo build --target wasm32-wasip1 --release
ls target/wasm32-wasip1/release/*.wasmThe compiled .wasm file is what Envoy will load. The cargo build --target wasm32-wasip1 command produces a release binary ready to be mounted into the Envoy container.
Once the binary exists, load it via the envoy.filters.http.wasm filter:
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: header_injector
root_id: header_injector_root
vm_config:
runtime: envoy.wasm.runtime.v8
vm_id: main_vm
code:
local:
filename: /etc/envoy/wasm/header_injector.wasm
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: "{}"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.RouterThe config.vm_config block specifies the V8 runtime and the WASM binary location. root_id connects the filter with the context the binary exports. Once loaded, every response will carry the x-wasm-filter header.
curl -sI -H "Host: api.example.com" http://localhost:10000/Grep the output for x-wasm-filter. The curl -sI command fetches headers only, enough to confirm the WASM filter works without downloading the body.
WASM can replace an external auth service for simple logic: check a token, verify a static secret, or enforce a policy the built-in filters don't have. The advantage is that decisions happen in-process without a gRPC round trip — lower latency than ext_authz.
A WASM filter can add context to every span or access log:
config:
name: trace_enhancer
root_id: trace_enhancer_root
vm_config:
runtime: envoy.wasm.runtime.v8
vm_id: telemetry_vm
code:
local:
filename: /etc/envoy/wasm/trace_enhancer.wasmWith WASM, you can read custom headers, compute derived values, and add them to access logs or tracing — enrichment that previously needed a native filter or a separate service.
Complex request transformations (path normalization, header additions based on the body) can be written in WASM, replacing a combination of several built-in filters. Full control over request and response in one programming language.
A few things to consider before WASM goes to production:
fail_open you set.curl -s localhost:9901/stats | grep "wasm"The wasm metrics show how many requests the VM processed and how many times the VM failed. Watch these metrics after your first WASM deployment.
Episode 16 opened the world of Envoy extensions: two extensibility paths, how to write and compile a WASM filter with Rust, load it into the pipeline, and three real use cases — custom auth, telemetry enrichment, and request transformation.
Key takeaways:
.wasm binary is loaded via the envoy.filters.http.wasm filter.wasm metrics before using WASM in production.In the next episode, episode 17, we'll discuss high availability and scaling — sidecar, gateway, and standalone deployment patterns, high availability for the xDS control plane, and multi-zone and multi-cluster considerations.