Understanding Envoy's Extension and Integration Mechanisms: From Custom Filters to Dynamic Modules
This article dives into various extension and integration mechanisms supported by Envoy, including custom C++ filter, Lua scripts, Wasm plugins, dynamic modules, and external integrations like ext_proc and ext_authz. I also share my personal preference and reasoning in favor of dynamic modules.

As Envoy gains widespread adoption in the cloud-native networking space, more developers are exploring how to extend its capabilities. Envoy supports multiple extension mechanisms, each with trade-offs in performance, security, development complexity, and compatibility.
This article summarizes my research into these mechanisms and aims to help you better understand and choose the right extension strategy.
Two Categories: Extension vs. Integration
When discussing “extension mechanisms” in Envoy, it’s important to distinguish between native in-process extensions and external integrations. Architecturally, these fall into two broad categories:
- In-process extension mechanisms:
- Custom C++ filters
- Lua scripts
- Wasm plugins
- Dynamic modules
- Out-of-process integration mechanisms:
- External Processing API (ext_proc)
- External Authorization API (ext_authz)
ext_proc
and ext_authz
rely on gRPC/HTTP APIs to call external services for request handling logic. They run outside the Envoy process and are not part of its filter chain. Thus, strictly speaking, they are integration mechanisms, not extensions.
However, since they are widely used in real-world scenarios for HTTP request/response handling, I’ve included them here for comparison.
Overview: Strengths and Weaknesses of Each Mechanism
Each mechanism differs in terms of implementation cost, performance, and applicable scenarios. Here’s a high-level comparison:
Custom C++ Filters: Top Performance, High Maintenance
This is the most primitive yet powerful way: write custom logic directly into Envoy’s source code and compile it. It delivers the best performance (zero-copy, low latency) and suits performance-critical paths. But the downsides include maintaining a custom build pipeline, distributing your own Envoy binaries, and high upgrade costs.
Lua Scripts: Lightweight and Flexible, but Limited Isolation
Lua is a mature extension option. It runs coroutine-based scripts in the same process as Envoy, within a LuaJIT runtime. It’s easy to use, requires no recompilation, and can be inline in the config. While Lua provides some level of sandboxing, it lacks the strong isolation of Wasm or external integrations — errors or resource exhaustion in Lua scripts could potentially affect Envoy itself. As such, Lua is best suited for trusted environments or lightweight tasks like header manipulation.
Wasm Plugins: Multi-language and Sandboxed, but Immature
Proxy-Wasm allows writing filters in Rust, Go, etc., compiled to WebAssembly modules dynamically loaded into Envoy. Wasm runs in a sandboxed VM with decent isolation. However, the ecosystem is still evolving, debugging is difficult, and performance is lower than C++ or dynamic modules.
Dynamic Modules: Native Alternative with Official Rust SDK
Dynamic Modules, introduced in Envoy v1.34, allow you to write extensions in languages like Rust or Go (compiled with a C ABI) and load them as .so
shared libraries at runtime. While the official SDK is currently available only in Rust, the examples repository also includes an experimental Go SDK, highlighting the potential for future multi-language support. Compared to custom C++ extensions, dynamic modules offer near-native performance without requiring a full Envoy rebuild, making them ideal for teams that need high performance but want to avoid maintaining a fork.
External Processing (ext_proc): Powerful but Latency-Heavy
ext_proc
enables complete customization of request/response logic in an external service—including reading/modifying the body. It’s useful for deep content inspection (DLP, antivirus, etc.), but being out-of-process, it introduces extra latency.
External Authorization (ext_authz): Lightweight Auth Integration
ext_authz is similar but only handles request path evaluation and cannot modify responses. It’s ideal for OAuth2, JWT, or header-based access control, commonly deployed remotely and non-intrusively.
Comparison Table: Mechanism Capabilities Across Dimensions
Here’s a detailed table comparing all six mechanisms across execution model, performance, language support, security, compatibility, and use cases:
Aspect | C++ Filter | Lua Script | Wasm | Dynamic Module | ext_proc | ext_authz |
---|---|---|---|---|---|---|
Execution Model | Native code inside Envoy process | LuaJIT coroutine (in-process) | VM-based (V8) | Shared object inside Envoy process | gRPC/REST, external service | gRPC/REST, external service |
Performance | Best (zero-copy) | Moderate (better than Wasm) | Moderate (cross-VM serialization) | High, near-native | Lower, cross-process cost | Efficient for metadata-only |
Language Support | C++ | Lua (stream API) | Best in Rust, supports Go/C++ | Rust official, Go possible | Any gRPC/REST language | Any gRPC/REST language |
Deployment | Statically compiled | Inline or script reference | Dynamically loaded .wasm | Dynamically loaded .so | Remote or sidecar service | Remote service or sidecar service |
Security/Isolation | Fully trusted | Limited isolation, full trust | Sandboxed, isolated | Shared memory, full trust required | Process isolation | Process isolation |
Compatibility | Strongly coupled to Envoy | Depends on Lua API stability | Relatively stable ABI | ABI-sensitive, version locked | Stable API, version-tolerant | Stable API, version-tolerant |
Use Cases | Core traffic path | Quick header edits, logic tweaks | Safe, cross-language, rapid prototype iteration | High-perf HTTP extensions, no build | DLP, security scans | Auth, access control |
Choosing the Right Mechanism
Each mechanism has its strengths and weaknesses, and the best choice depends on your specific use case. Factors such as performance requirements, development resources, and operational constraints should inform your decision. Instead of favoring one approach, it’s important to evaluate the trade-offs and select the mechanism that aligns with your needs.
For example, my recent research into dynamic modules highlights their potential as a balanced solution for extending Envoy. They provide near-C++ performance without the complexity of rebuilding Envoy, making them an elegant compromise for teams that need high performance but want to avoid maintaining a forked Envoy.
Compared to Wasm, dynamic modules run directly in-process—no serialization, no VM overhead, no memory sandbox—which gives them a natural advantage for header and body manipulation. While they are still experimental and lack ABI compatibility across versions, this can be managed in environments with fixed or controlled release cycles.
Dynamic modules are just one of many options, and their suitability depends on your specific requirements. By understanding the trade-offs of each mechanism, you can make an informed decision that best fits your operational needs.
In my view, dynamic modules are poised to replace many Wasm use cases, especially in enterprise environments where performance and debuggability matter.
I’ll share a complete tutorial on building a dynamic module in Rust soon—stay tuned!
Conclusion: How to Choose the Right Mechanism?
Here’s my recommendation:
- For high-performance needs without managing custom Envoy builds, dynamic modules are the best balance of flexibility and speed. Though still experimental, they are worth serious consideration.
- C++ filters remain the gold standard for performance but require significant maintenance.
- For quick logic hacks or header rewrites, Lua is the easiest to adopt.
- For secure, multi-language, sandboxed development, Wasm is powerful but currently limited by ecosystem maturity and performance.
- If you want to decouple logic from Envoy entirely, ext_proc and ext_authz provide safe and flexible integration points—perfect for auth, policy, and external logic enforcement.
No single mechanism is a silver bullet. The key is understanding their design trade-offs and selecting based on your operational needs.