WASM Edge Gateway: 5 Core Designs for Building Plugin-Based API Gateway with Go
Your API Gateway Requires a Full Release Just to Add a Feature
At 2 AM, an urgent rate limit policy change comes in — you discover Kong plugins only support Lua, APISIX plugins only run in OpenResty, and Envoy native Filters require recompiling the entire C++ binary. Plugin language constraints, no hot updates, poor security isolation — every feature change is a full release, and the gateway team becomes the bottleneck for the entire company.
In 2026, the WASM Edge Gateway changes everything. Go host process loads WASM plugins, choose TinyGo/Rust/AssemblyScript, plugins run in sandboxes they cannot escape, hot reload with zero downtime, and seamless Envoy WASM Filter integration. This guide walks you through 5 core designs, from gateway architecture to Go host runtime, from WASM plugin development to Envoy Filter integration, from edge deployment to hot reload, with complete code throughout.
WASM Edge Gateway Core Concepts
| Concept | Description |
|---|---|
| WASM Gateway | API gateway based on WebAssembly plugin system, plugins developed in multiple languages, sandboxed, hot-loadable |
| WASM Plugin | Gateway extension module compiled to .wasm binary, runs in sandbox, supports auth, rate limiting, transformation |
| Envoy WASM Filter | Envoy proxy WASM filter, developed with Proxy-WASM SDK, embeddable in Go/C++/Rust |
| Proxy-WASM | Envoy's WASM proxy interface specification, defines lifecycle hooks like onHttpRequest/onHttpResponse |
| Edge Routing | API routing rules executed at CDN/edge nodes, dispatching requests to nearest backend services |
| Plugin Sandbox | WASM linear memory isolation model, plugins cannot access host process memory or system resources |
| Hot Reload | Runtime replacement of WASM plugin modules without restarting the gateway process, zero request loss |
WASM Edge Gateway Architecture
┌──────────────────────────────────────────────────────┐
│ Edge Gateway (Go) │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Route │ │ Plugin │ │ Plugin Chain │ │
│ │ Matcher │ │ Loader │ │ ┌──┐┌──┐┌──┐ │ │
│ └────┬─────┘ └─────┬─────┘ │ │Au││RL││Co│ │ │
│ │ │ │ │th││im││RS│ │ │
│ ┌────▼──────────────▼────┐ │ └──┘└──┘└──┘ │ │
│ │ Wazero Runtime │ └────────┬─────────┘ │
│ │ ┌──────┐ ┌──────┐ │ │ │
│ │ │Auth │ │Rate │ │ ┌─────────▼──────────┐ │
│ │ │WASM │ │Limit │ │ │ Reverse Proxy │ │
│ │ │Plugin│ │WASM │ │ │ (Upstream) │ │
│ │ └──────┘ └──────┘ │ └────────────────────┘ │
│ └───────────────────────┘ │
└──────────────────────────────────────────────────────┘
Problem Analysis: 5 Limitations of Traditional API Gateways
- Plugin language constraints: Kong only supports Lua, APISIX only supports Lua/Go (requires compilation), Envoy native Filters require C++ recompilation — your team's tech stack is held hostage by the gateway
- No hot updates: Modifying plugin logic requires restarting the gateway process or reloading configuration — high risk and long maintenance windows
- Poor security isolation: Lua plugins directly access host process memory — a single malicious or buggy plugin can crash the entire gateway instance
- High extension cost: Adding new auth methods or custom rate limiting requires deep understanding of gateway source code, long development cycles, and broad regression testing
- Inconsistent multi-gateway: Kong/APISIX/Envoy/Nginx each have their own plugin systems — cross-gateway policies cannot be reused, doubling maintenance costs
Design 1: WASM Gateway Architecture
The core idea of a WASM gateway is decomposing gateway functionality into pluggable WASM modules. The Go host handles route dispatching and plugin orchestration, while WASM plugins handle specific business logic.
package gateway
type GatewayConfig struct {
ListenAddr string `json:"listenAddr"`
Routes []RouteConfig `json:"routes"`
PluginChain []PluginConfig `json:"pluginChain"`
UpstreamPool map[string]string `json:"upstreamPool"`
}
type RouteConfig struct {
Path string `json:"path"`
Methods []string `json:"methods"`
PluginChain []string `json:"pluginChain"`
Upstream string `json:"upstream"`
Rewrite string `json:"rewrite,omitempty"`
}
type PluginConfig struct {
Name string `json:"name"`
WasmURL string `json:"wasmUrl"`
Config []byte `json:"config"`
}
type EdgeGateway struct {
config GatewayConfig
runtime *WASMRuntime
routes *RouteMatcher
plugins *PluginChainManager
proxy *ReverseProxy
}
func NewEdgeGateway(cfg GatewayConfig) (*EdgeGateway, error) {
rt, err := NewWASMRuntime()
if err != nil {
return nil, fmt.Errorf("init wasm runtime: %w", err)
}
pcm, err := NewPluginChainManager(rt, cfg.PluginChain)
if err != nil {
return nil, fmt.Errorf("init plugin chain: %w", err)
}
return &EdgeGateway{
config: cfg,
runtime: rt,
routes: NewRouteMatcher(cfg.Routes),
plugins: pcm,
proxy: NewReverseProxy(cfg.UpstreamPool),
}, nil
}
Design 2: Go Host Runtime Implementation
Using the wazero runtime to load WASM plugins, each plugin runs in an independent sandbox, implementing request/response interception through exported functions.
package gateway
import (
"context"
"net/http"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)
type WASMPlugin struct {
Name string
runtime wazero.Runtime
module api.Module
}
type WASMRuntime struct {
runtime wazero.Runtime
ctx context.Context
plugins map[string]*WASMPlugin
}
func NewWASMRuntime() (*WASMRuntime, error) {
ctx := context.Background()
rt := wazero.NewRuntime(ctx)
_, err := rt.NewHostModuleBuilder("env").
NewFunctionBuilder().
WithFunc(func(m api.Module, offset uint32, size uint32) {
buf := make([]byte, size)
m.Memory().Read(offset, buf)
}).
Export("log").
NewFunctionBuilder().
WithFunc(func(m api.Module, statusCode uint32, bodyOff uint32, bodyLen uint32) {
buf := make([]byte, bodyLen)
m.Memory().Read(bodyOff, buf)
}).
Export("sendLocalReply").
Instantiate(ctx)
if err != nil {
return nil, err
}
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
return &WASMRuntime{
runtime: rt,
ctx: ctx,
plugins: make(map[string]*WASMPlugin),
}, nil
}
func (wr *WASMRuntime) LoadPlugin(name string, wasmBytes []byte) (*WASMPlugin, error) {
mod, err := wr.runtime.Instantiate(wr.ctx, wasmBytes)
if err != nil {
return nil, fmt.Errorf("instantiate plugin %s: %w", name, err)
}
plugin := &WASMPlugin{
Name: name,
runtime: wr.runtime,
module: mod,
}
wr.plugins[name] = plugin
return plugin, nil
}
func (p *WASMPlugin) ExecuteRequestFilter(req *http.Request) (*http.Response, bool) {
ctx := context.Background()
fn := p.module.ExportedFunction("onHttpRequest")
if fn == nil {
return nil, true
}
results, err := fn.Call(ctx)
if err != nil {
return nil, true
}
return nil, results[0] == 0
}
func (p *WASMPlugin) ExecuteResponseFilter(resp *http.Response) bool {
ctx := context.Background()
fn := p.module.ExportedFunction("onHttpResponse")
if fn == nil {
return true
}
results, err := fn.Call(ctx)
if err != nil {
return true
}
return results[0] == 0
}
Design 3: WASM Plugin Development
Using TinyGo to write auth and rate limiting plugins, compiled to WASI target, exporting onHttpRequest and onHttpResponse lifecycle hooks.
package main
import "unsafe"
var memoryBuffer []byte
//export allocate
func allocate(size int32) unsafe.Pointer {
memoryBuffer = make([]byte, size)
return unsafe.Pointer(&memoryBuffer[0])
}
//export onHttpRequest
func onHttpRequest() int32 {
headers := getHeaders()
token := getHeader(headers, "Authorization")
if token == "" {
sendLocalReply(401, "Unauthorized")
return 1
}
if !validateToken(token) {
sendLocalReply(403, "Forbidden")
return 1
}
return 0
}
//export onHttpResponse
func onHttpResponse() int32 {
addHeader("X-Response-Time", getCurrentTime())
addHeader("X-Gateway-Version", "1.0.0")
return 0
}
func validateToken(token string) bool {
if len(token) < 10 {
return false
}
return true
}
func getHeaders() map[string]string {
return make(map[string]string)
}
func getHeader(headers map[string]string, key string) string {
return headers[key]
}
func sendLocalReply(statusCode int32, body string) {}
func addHeader(key string, value string) {}
func getCurrentTime() string {
return "2026-06-18T00:00:00Z"
}
func main() {}
Rate limiting plugin example:
package main
import "unsafe"
var requestCount int32 = 0
var maxRequests int32 = 1000
//export onHttpRequest
func onHttpRequest() int32 {
requestCount++
if requestCount > maxRequests {
sendLocalReply(429, "Too Many Requests")
return 1
}
addHeader("X-RateLimit-Remaining", int32ToStr(maxRequests-requestCount))
return 0
}
func int32ToStr(n int32) string {
if n == 0 {
return "0"
}
var buf [12]byte
pos := len(buf)
for n > 0 {
pos--
buf[pos] = byte('0' + n%10)
n /= 10
}
return string(buf[pos:])
}
func sendLocalReply(code int32, body string) {}
func addHeader(key, val string) {}
func main() {}
Build commands:
tinygo build -o auth-plugin.wasm -target=wasi -no-debug -scheduler=none .
tinygo build -o ratelimit-plugin.wasm -target=wasi -no-debug -scheduler=none .
Design 4: Envoy WASM Filter Integration
Developing Envoy-compatible WASM Filters through the Proxy-WASM SDK, enabling seamless integration with Envoy/Istio service mesh.
package main
import (
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)
func main() {
proxywasm.SetVMContext(&vmContext{})
}
type vmContext struct {
types.DefaultVMContext
}
func (v *vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}
type pluginContext struct {
types.DefaultPluginContext
maxRequests int32
}
func (p *pluginContext) OnPluginStart(configurationSize int) types.OnPluginStartStatus {
data, err := proxywasm.GetPluginConfiguration(configurationSize)
if err != nil && err != types.ErrorStatusNotFound {
proxywasm.LogCriticalf("error reading config: %v", err)
return types.OnPluginStartStatusFailed
}
if len(data) > 0 {
p.maxRequests = parseInt32(data)
}
return types.OnPluginStartStatusOK
}
func (p *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &httpContext{maxRequests: p.maxRequests}
}
type httpContext struct {
types.DefaultHttpContext
maxRequests int32
requestCount int32
}
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
ctx.requestCount++
if ctx.requestCount > ctx.maxRequests {
proxywasm.SendLocalReply(429, "rate limited", nil, 0, "")
return types.ActionPause
}
token, err := proxywasm.GetHttpRequestHeader("authorization")
if err != nil || token == "" {
proxywasm.SendLocalReply(401, "unauthorized", nil, 0, "")
return types.ActionPause
}
proxywasm.AddHttpRequestHeader("x-wasm-filter", "envoy-auth-v1")
return types.ActionContinue
}
func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
proxywasm.AddHttpResponseHeader("x-wasm-processed", "true")
return types.ActionContinue
}
func parseInt32(data []byte) int32 {
var result int32
for _, b := range data {
if b >= '0' && b <= '9' {
result = result*10 + int32(b-'0')
}
}
return result
}
Envoy configuration integration:
name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: auth-filter
root_id: auth-filter
vm_config:
vm_id: auth-filter-vm
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /etc/envoy/auth-filter.wasm
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: "1000"
Design 5: Edge Deployment and Hot Reload
Implementing plugin remote loading and version management, supporting WASM module fetching from S3/OSS, zero-downtime version rollback.
package gateway
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"sync"
"sync/atomic"
"time"
)
type PluginVersion struct {
Name string `json:"name"`
Version string `json:"version"`
SHA256 string `json:"sha256"`
WasmURL string `json:"wasmUrl"`
LoadedAt int64 `json:"loadedAt"`
}
type PluginRegistry struct {
runtime *WASMRuntime
versions map[string]*PluginVersion
current sync.Map
mu sync.RWMutex
httpClient *http.Client
}
func NewPluginRegistry(rt *WASMRuntime) *PluginRegistry {
return &PluginRegistry{
runtime: rt,
versions: make(map[string]*PluginVersion),
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (pr *PluginRegistry) LoadFromRemote(name string, version PluginVersion) error {
resp, err := pr.httpClient.Get(version.WasmURL)
if err != nil {
return fmt.Errorf("fetch wasm from %s: %w", version.WasmURL, err)
}
defer resp.Body.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, resp.Body); err != nil {
return fmt.Errorf("read wasm body: %w", err)
}
wasmBytes := buf.Bytes()
hash := sha256.Sum256(wasmBytes)
checksum := hex.EncodeToString(hash[:])
if checksum != version.SHA256 {
return fmt.Errorf("checksum mismatch: expected %s, got %s", version.SHA256, checksum)
}
plugin, err := pr.runtime.LoadPlugin(name, wasmBytes)
if err != nil {
return fmt.Errorf("load plugin %s: %w", name, err)
}
pr.mu.Lock()
old, exists := pr.versions[name]
pr.versions[name] = &version
pr.mu.Unlock()
pr.current.Store(name, plugin)
if exists {
fmt.Printf("plugin %s upgraded: %s -> %s\n", name, old.Version, version.Version)
} else {
fmt.Printf("plugin %s loaded: v%s\n", name, version.Version)
}
return nil
}
type PluginChainManager struct {
registry *PluginRegistry
chains map[string][]string
mu sync.RWMutex
}
func NewPluginChainManager(rt *WASMRuntime, configs []PluginConfig) (*PluginChainManager, error) {
registry := NewPluginRegistry(rt)
pcm := &PluginChainManager{
registry: registry,
chains: make(map[string][]string),
}
for _, cfg := range configs {
version := PluginVersion{
Name: cfg.Name,
Version: "1.0.0",
WasmURL: cfg.WasmURL,
}
if err := registry.LoadFromRemote(cfg.Name, version); err != nil {
return nil, fmt.Errorf("load plugin %s: %w", cfg.Name, err)
}
}
return pcm, nil
}
func (pcm *PluginChainManager) ExecuteChain(chainName string, req *http.Request) (*http.Response, bool) {
pcm.mu.RLock()
chain, exists := pcm.chains[chainName]
pcm.mu.RUnlock()
if !exists {
return nil, true
}
for _, pluginName := range chain {
plugin, ok := pcm.registry.current.Load(pluginName)
if !ok {
continue
}
wasmPlugin := plugin.(*WASMPlugin)
resp, allowed := wasmPlugin.ExecuteRequestFilter(req)
if !allowed {
return resp, false
}
}
return nil, true
}
Complete gateway routing + plugin chain example:
package main
import (
"log"
"net/http"
"github.com/yourorg/edge-gateway/gateway"
)
func main() {
cfg := gateway.GatewayConfig{
ListenAddr: ":8080",
Routes: []gateway.RouteConfig{
{
Path: "/api/v1/users",
Methods: []string{"GET", "POST"},
PluginChain: []string{"auth-plugin", "ratelimit-plugin"},
Upstream: "user-service",
},
{
Path: "/api/v1/orders",
Methods: []string{"GET", "POST", "PUT"},
PluginChain: []string{"auth-plugin", "ratelimit-plugin", "cors-plugin"},
Upstream: "order-service",
},
},
PluginChain: []gateway.PluginConfig{
{Name: "auth-plugin", WasmURL: "https://cdn.example.com/plugins/auth-v1.2.0.wasm"},
{Name: "ratelimit-plugin", WasmURL: "https://cdn.example.com/plugins/ratelimit-v1.1.0.wasm"},
{Name: "cors-plugin", WasmURL: "https://cdn.example.com/plugins/cors-v1.0.0.wasm"},
},
UpstreamPool: map[string]string{
"user-service": "http://127.0.0.1:8081",
"order-service": "http://127.0.0.1:8082",
},
}
gw, err := gateway.NewEdgeGateway(cfg)
if err != nil {
log.Fatalf("create gateway: %v", err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", gw.HandleRequest)
log.Printf("WASM Edge Gateway listening on %s", cfg.ListenAddr)
log.Fatal(http.ListenAndServe(cfg.ListenAddr, mux))
}
Pitfall Guide: 5 Common Traps
Trap 1: WASM Plugins Directly Operating HTTP Connections
// ❌ Wrong: Cannot establish TCP connections inside WASM sandbox
conn, _ := net.Dial("tcp", "backend:8080")
// ✅ Correct: Proxy network requests through host-provided Host Functions
results, _ := hostNetDial.Call(ctx, uint64(offset), uint64(len(host)), uint64(port))
Trap 2: Shared State Between Plugins Causing Data Races
// ❌ Wrong: Multiple WASM plugin instances sharing global variables
var tokenCache = make(map[string]bool)
// ✅ Correct: Each plugin instance uses independent memory, state shared through host
// Host side provides KV Store Host Function
Trap 3: Ignoring WASM Plugin Execution Timeout
// ❌ Wrong: Wait indefinitely for plugin return, malicious/infinite-loop plugins block entire request
results, err := fn.Call(ctx)
// ✅ Correct: Use context.WithTimeout to control execution time
timeoutCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
results, err := fn.Call(timeoutCtx)
Trap 4: Not Handling In-Flight Requests During Hot Reload
// ❌ Wrong: Directly unload old module, in-flight requests access freed memory
oldModule.Close(ctx)
// ✅ Correct: Reference counting + graceful wait, unload only after all requests complete
atomic.AddInt32(&refCount, 1)
defer atomic.AddInt32(&refCount, -1)
// During hot reload, wait for refCount to reach 0 before unloading
Trap 5: TinyGo Build Without WASI Target
# ❌ Wrong: Using -wasm target causes missing WASI imports
tinygo build -o plugin.wasm -target=wasm .
# ✅ Correct: Use wasi target for standard system call support
tinygo build -o plugin.wasm -target=wasi -no-debug -scheduler=none .
Error Troubleshooting: 10 Common Errors
| # | Error Message | Cause | Solution |
|---|---|---|---|
| 1 | out of bounds memory access |
Host reads/writes WASM memory offset out of bounds | Check allocate return value and data length calculation |
| 2 | function export not found: onHttpRequest |
WASM module does not export lifecycle hook | Ensure TinyGo exports //export onHttpRequest during compilation |
| 3 | import not found: wasi_snapshot_preview1 |
WASI module not initialized | Call wasi_snapshot_preview1.MustInstantiate |
| 4 | instantiation error: memory size exceeds limit |
Plugin initial memory exceeds limit | Increase WithMemoryLimitPages or optimize memory usage |
| 5 | context deadline exceeded in plugin execution |
Plugin execution timeout | Use context.WithTimeout and check plugin logic |
| 6 | checksum mismatch |
Remote loaded WASM file verification failed | Confirm file integrity, re-upload WASM module |
| 7 | plugin version conflict |
Same-name plugin version conflict | Use semantic versioning, ensure uniqueness |
| 8 | goroutine stack overflow in wasm |
TinyGo default scheduler causes stack overflow | Add -scheduler=none during compilation |
| 9 | wasm validation error: invalid section |
WASM binary file corrupted | Recompile WASM module, check build command |
| 10 | hot reload: old module still referenced |
Old module references not released during hot reload | Ensure request reference count reaches zero before unloading |
Advanced Optimization Tips
1. WASM Plugin Pre-compiled Cache
type CompiledCache struct {
cache map[string]wazero.CompiledModule
mu sync.RWMutex
}
func (cc *CompiledCache) GetOrCompile(ctx context.Context, rt wazero.Runtime, key string, wasmBytes []byte) (wazero.CompiledModule, error) {
cc.mu.RLock()
if compiled, ok := cc.cache[key]; ok {
cc.mu.RUnlock()
return compiled, nil
}
cc.mu.RUnlock()
compiled, err := rt.CompileModule(ctx, wasmBytes)
if err != nil {
return nil, err
}
cc.mu.Lock()
cc.cache[key] = compiled
cc.mu.Unlock()
return compiled, nil
}
2. Plugin Chain Parallel Execution
func (pcm *PluginChainManager) ExecuteChainParallel(chainName string, req *http.Request) (*http.Response, bool) {
pcm.mu.RLock()
chain := pcm.chains[chainName]
pcm.mu.RUnlock()
type result struct {
resp *http.Response
allowed bool
}
ch := make(chan result, len(chain))
for _, pluginName := range chain {
go func(name string) {
plugin, _ := pcm.registry.current.Load(name)
if plugin == nil {
ch <- result{nil, true}
return
}
resp, allowed := plugin.(*WASMPlugin).ExecuteRequestFilter(req)
ch <- result{resp, allowed}
}(pluginName)
}
for i := 0; i < len(chain); i++ {
r := <-ch
if !r.allowed {
return r.resp, false
}
}
return nil, true
}
3. Plugin Metrics Collection
type PluginMetrics struct {
Name string
ExecCount atomic.Int64
ErrorCount atomic.Int64
TotalTimeNs atomic.Int64
}
func (m *PluginMetrics) Record(execTime time.Duration, hasError bool) {
m.ExecCount.Add(1)
m.TotalTimeNs.Add(execTime.Nanoseconds())
if hasError {
m.ErrorCount.Add(1)
}
}
func (m *PluginMetrics) AvgTime() time.Duration {
count := m.ExecCount.Load()
if count == 0 {
return 0
}
return time.Duration(m.TotalTimeNs.Load() / count)
}
4. Plugin Configuration Hot Injection
func (wr *WASMRuntime) InjectConfig(pluginName string, configJSON []byte) error {
plugin, ok := wr.plugins[pluginName]
if !ok {
return fmt.Errorf("plugin %s not found", pluginName)
}
fn := plugin.module.ExportedFunction("onConfigUpdate")
if fn == nil {
return nil
}
offset, err := writeString(plugin.module, string(configJSON))
if err != nil {
return err
}
_, err = fn.Call(wr.ctx, uint64(offset), uint64(len(configJSON)))
return err
}
5. Edge Node Plugin Distribution
type EdgeDistributor struct {
registry *PluginRegistry
nodes []string
httpClient *http.Client
}
func (ed *EdgeDistributor) PushToEdge(pluginName string, version PluginVersion) error {
for _, node := range ed.nodes {
url := fmt.Sprintf("http://%s/api/v1/plugins/load", node)
payload, _ := json.Marshal(map[string]interface{}{
"name": pluginName,
"version": version.Version,
"wasmUrl": version.WasmURL,
"sha256": version.SHA256,
})
resp, err := ed.httpClient.Post(url, "application/json", bytes.NewReader(payload))
if err != nil {
fmt.Printf("push to %s failed: %v\n", node, err)
continue
}
resp.Body.Close()
fmt.Printf("pushed %s v%s to %s\n", pluginName, version.Version, node)
}
return nil
}
Comparison: WASM Gateway vs Kong vs APISIX vs Envoy Native
| Dimension | WASM Gateway (Go) | Kong | APISIX | Envoy Native |
|---|---|---|---|---|
| Plugin Language | Go/TinyGo/Rust/AS | Lua | Lua/Go (external) | C++ |
| Hot Reload | WASM module hot-load | Requires restart/DB sync | etcd watch | Requires binary recompilation |
| Security Isolation | WASM sandbox strong isolation | Lua shared process memory | Lua shared process memory | Process-level isolation |
| Plugin Size | 100KB-2MB | Lua script <50KB | Lua script <50KB | Requires recompiling entire binary |
| Execution Performance | Near-native (90-95%) | LuaJIT high performance | LuaJIT high performance | Native C++ fastest |
| Multi-gateway Reuse | WASM module universal | Kong only | APISIX only | Envoy only |
| Learning Curve | Requires WASM+Go knowledge | Lua simple | Lua simple | C++ high barrier |
| Ecosystem Maturity | Rapid growth (2026) | Mature (500+ plugins) | Mature (100+ plugins) | Mature (Istio ecosystem) |
Recommended Online Tools
When developing WASM edge gateways, the following online tools can significantly boost productivity:
- JSON Formatter — Quickly format and validate JSON when debugging gateway plugin configurations and routing rules
- Hash Calculator — Calculate SHA256 checksums for WASM modules to verify plugin integrity during remote loading
- Curl to Code Converter — Convert curl requests to Go HTTP code for quickly writing gateway test cases
Summary and Outlook
The WASM edge gateway doesn't replace Kong or Envoy — it provides a layer of language-agnostic, secure, hot-reloadable plugin abstraction on top of them. The 2026 trend is: Envoy as the data plane proxy, WASM Filter as the portable plugin layer, and Go host as the lightweight gateway orchestrator. Combined, you get Envoy's production-grade stability + WASM's plugin flexibility + Go's development efficiency. Next, watch for the WASM Component Model and WASI Preview 3 standardization progress — they will completely solve cross-language plugin interoperability.
Further Reading
- Proxy-WASM ABI Specification — Envoy WASM Filter interface specification
- Wazero Runtime Documentation — Pure Go WASM runtime official docs
- Envoy WASM Filter Design — Envoy WASM filter design documentation
- WebAssembly Component Model — WASM component model proposal
- TinyGo WASI Target Guide — TinyGo WASI compilation guide
Try these browser-local tools — no sign-up required →