Zuplo
API Gateway

API Gateway Plugin Architectures Compared (2026): Lua, Go, WASM, and TypeScript

Nate TottenNate Totten
May 8, 2026
15 min read

Compare API gateway plugin architectures — Lua (Kong, APISIX), Go (Tyk), C++/WASM (Envoy), njs (NGINX), Yaegi (Traefik), and TypeScript (Zuplo) — with tradeoffs, code examples, and a decision framework.

Every API gateway offers some way to run custom logic on incoming requests. What separates them is how that logic is authored, packaged, and deployed — and those differences have real consequences for your team’s velocity, your hiring pipeline, and your debugging story at 2 a.m.

This guide compares the plugin and extension architectures across six major API gateway platforms: Kong, Apache APISIX, Tyk, Envoy (including Istio), NGINX, and Zuplo. The goal isn’t to declare a winner — it’s to give you enough technical detail to choose the right extensibility model for your team and workload.

What “Plugin Architecture” Actually Controls

Before diving into specific platforms, it helps to understand what an API gateway plugin architecture determines:

  • Where extensions run — in-process (same memory space as the proxy), sandboxed (isolated VM), or out-of-process (separate service via gRPC or IPC)
  • What language you write in — Lua, Go, C++, Rust, TypeScript, or something else entirely
  • How plugins are packaged and distributed — compiled binaries, source files, WASM modules, LuaRocks packages, or npm bundles
  • What extensions can mutate — request headers, body, routing decisions, response headers, response body, or some subset
  • How configuration propagates — database polling, etcd watch, filesystem reload, or git-based atomic deployment

These factors directly shape your developer experience, operational burden, and how quickly you can ship changes to your API layer.

Lua Plugins: Kong and Apache APISIX

Kong and Apache APISIX are both built on OpenResty, which embeds LuaJIT into NGINX worker processes. Lua plugins run in-process, hook into NGINX’s phase-based request lifecycle, and share the same high-performance event loop as the proxy itself.

How Lua Plugins Work

A Kong plugin consists of two core files: handler.lua (the logic) and schema.lua (configuration validation). Plugins register handlers for specific request lifecycle phases — rewrite, access, header_filter, body_filter, log, and others. Each phase runs at a specific point in the request lifecycle, and a priority number controls execution order within each phase.

The Plugin Development Kit (PDK) exposes gateway internals through a global kong table — kong.request, kong.response, kong.service, kong.log — and is guaranteed forward-compatible from Kong 1.0.

APISIX follows a similar model but uses etcd instead of PostgreSQL or Cassandra as its configuration store. This is a meaningful architectural difference: etcd’s watch mechanism pushes configuration changes to all gateway nodes in milliseconds, while Kong’s database polling (with a default db_update_frequency of 5 seconds) means other nodes may take several seconds to see configuration changes.

Strengths

  • Near-native performance. LuaJIT compiles hot code paths to machine code. Cloudflare famously pushes OpenResty/Lua to extreme scale.
  • Mature ecosystem. Kong Hub lists hundreds of community and enterprise-maintained plugins. APISIX ships with roughly 100 built-in plugins.
  • Lua plugin hot-reload (APISIX). APISIX can add, delete, and modify Lua plugins without restarting the gateway — a genuine operational advantage.
  • Battle-tested at scale. Both gateways have years of production deployment across thousands of organizations.

Tradeoffs

  • Lua is a specialist language. The developer pool for Lua is a fraction of JavaScript, Python, Go, or TypeScript. Hiring and onboarding take longer.
  • Non-Lua support is limited. Kong offers Go, JavaScript, and Python PDK wrappers, but these are IPC-based wrappers around the Lua runtime — not first-class runtimes. APISIX supports external Plugin Runners (Java, Go, Python, Node.js) that communicate over Unix sockets, adding IPC overhead.
  • Debugging is harder. Lua tooling (profilers, debuggers, IDE support) is less mature than what Go or TypeScript developers expect.
  • Adding new plugin code to Kong requires a restart. Configuration changes propagate without downtime, but deploying a previously unknown custom plugin requires kong reload or a full restart.

Who Should Choose Lua-Based Gateways

Teams with existing Lua expertise, large self-hosted Kubernetes deployments where Kong or APISIX is already established, and organizations that need the deepest community plugin ecosystem.

For a deeper look at Kong and APISIX compared to managed alternatives, see the Kong vs. Zuplo and APISIX vs. Zuplo comparison pages.

Go Plugins: Tyk

Tyk is written in Go and processes requests through a middleware chain. It offers four distinct plugin mechanisms, each with different tradeoffs.

How Go Plugins Work

Tyk’s highest-performance option is Go native plugins. You write a Go function, compile it as a shared library (.so file) using go build -buildmode=plugin, and Tyk loads it into its process at runtime. The plugin runs in the same memory space as the gateway — no serialization, no IPC.

For teams that don’t want to manage Go compilation, Tyk also supports:

  • gRPC plugins — an external process in any language communicates with Tyk over gRPC at configured points in the middleware chain
  • JavaScript middleware (JSVM) — an embedded ECMAScript 5 interpreter for simple pre/post hooks (not full ES2023)
  • Python plugins — external process, similar to the gRPC model

Plugins hook into five lifecycle points: Pre (before auth), Auth (custom authentication), Post-Auth (after auth, before upstream), Post (after upstream response), and Response (response body/header manipulation).

Strengths

  • In-process Go performance. Native Go plugins run without IPC overhead — the fastest option for Go teams.
  • True polyglot via gRPC. Any language with a gRPC implementation can extend Tyk, running as an external service.
  • Type safety. Go’s type system catches errors at compile time rather than runtime.

Tradeoffs

  • Go plugin version compatibility. This is the most commonly cited pain point. The plugin .so must be compiled with the exact same Go toolchain version as the Tyk Gateway binary. All shared dependencies must match exactly. CGO_ENABLED=1 is required. A mismatch causes runtime crashes with no clear error message. Every Tyk Gateway upgrade potentially requires recompiling all custom plugins.
  • Plugin distribution is manual. Tyk uses ZIP bundles uploaded to a bundle server (Mserv). There’s no centralized plugin marketplace.
  • JSVM is limited. The embedded JavaScript engine supports ES5 only and is restricted to pre/post hooks — no response manipulation, no modern JavaScript features.
  • Smaller community plugin ecosystem compared to Kong or APISIX.

Who Should Choose Go-Based Gateways

Teams that are primarily Go shops, need the gRPC escape hatch for polyglot extensions, and are willing to manage the build-toolchain alignment for native plugins.

See the Tyk vs. Zuplo comparison for a broader platform evaluation.

C++ and WASM Filters: Envoy and Istio

Envoy has the most layered extension architecture of any gateway in this comparison, with five distinct mechanisms ranging from native C++ to external process calls.

How Envoy Extensions Work

Envoy’s extension model reflects its origin as a high-performance service mesh proxy:

  • Native C++ filters — compiled into the Envoy binary itself. Zero-copy, maximum performance, but requires maintaining a custom Envoy build with Bazel. The barrier to entry is high.
  • WASM filters — WebAssembly modules loaded at runtime via the proxy-wasm specification. The plugin runs in a sandboxed VM (V8, Wasmtime, or WAMR) with isolated memory per worker thread.
  • Dynamic modules (new in Envoy v1.34) — Rust or Go code compiled to shared libraries with a C ABI. Near-native performance without requiring a custom Envoy build.
  • Lua filters — inline Lua scripting for simpler transformations.
  • ext_proc / ext_authz — full process isolation via gRPC or HTTP calls to an external service. Maximum language freedom, lowest performance.

WASM: The Promising Middle Ground

WASM is the most interesting extension mechanism for most teams. Plugin authors write code using a proxy-wasm SDK (available for Rust, C++, TinyGo, and AssemblyScript), compile to a .wasm binary, and Envoy loads it at runtime. WASM filters can be delivered from the control plane without redeploying Envoy — a genuine operational win.

Istio leverages this through its WasmPlugin CRD, distributing .wasm binaries to sidecar proxies across the mesh and managing their lifecycle.

Strengths

  • Multiple extension models for different tradeoffs. Teams can choose the right mechanism for each use case — native C++ for hot-path performance, WASM for portable sandboxed extensions, ext_proc for full language freedom.
  • WASM sandboxing. A crashing or leaking plugin cannot bring down the proxy.
  • proxy-wasm is becoming an industry standard. The ABI works across multiple proxies, creating potential for a cross-proxy plugin ecosystem.
  • Deep service mesh integration. If you’re running Istio, Envoy extensions are the native extensibility path.

Tradeoffs

  • WASM performance overhead. Request data must be copied into and out of the WASM VM memory. Industry estimates cite measurable overhead compared to native C++ filters, primarily due to serialization and memory copying at the VM boundary.
  • TinyGo is not standard Go. The WASM-compatible Go compiler (TinyGo) is a subset of Go with missing standard library packages. This frustrates Go developers who expect their existing code to “just compile.”
  • Debugging is significantly harder. WASM extensions are opaque to standard debuggers. Source maps are limited, and error messages from the WASM runtime are often cryptic.
  • Decision fatigue. Five extension mechanisms means teams must evaluate which one fits each use case. There is no single “right way” to extend Envoy.
  • No centralized plugin hub. Extensions are distributed as files — .wasm binaries, .so files, or compiled into the binary. There is no marketplace.

Who Should Choose WASM/Envoy

Teams already invested in Envoy or Istio for service mesh, workloads where WASM sandboxing is a security requirement, and platform teams with the engineering capacity to manage multiple extension mechanisms.

See the Envoy vs. Zuplo comparison and Envoy as Your API Gateway for more context.

NGINX Lua and njs: NGINX Plus and NGINX OSS

NGINX is not an API gateway with a plugin framework — it’s a web server and reverse proxy extended via modules. Two scripting runtimes are available: Lua (via OpenResty) and njs (NGINX JavaScript).

How NGINX Extensions Work

Lua via OpenResty embeds LuaJIT into NGINX workers and integrates Lua coroutines with NGINX’s event loop. The lua-resty-* library ecosystem provides non-blocking I/O for Redis, MySQL, PostgreSQL, and more. This is the same foundation that powers Kong and APISIX.

njs is NGINX’s built-in JavaScript runtime — but it is not Node.js. This is the most important thing to understand about njs:

  • njs historically supported ES5.1 with some ES6 extensions. As of njs 0.9.1+, a QuickJS engine backend brings ES2023 support — a significant improvement.
  • There is no npm module support. Packages must be manually transpiled and bundled.
  • js_set handlers are synchronous. Async capabilities exist (ngx.fetch(), subrequests) but are limited compared to Node.js.
  • The runtime runs inside the NGINX process, not on V8 or libuv.

Strengths

  • OpenResty Lua is proven at planet scale. Cloudflare’s entire edge runs on it. Performance is exceptional.
  • njs is simple for basic tasks. For straightforward header manipulation or request routing logic, njs is easy to get started with.
  • No external dependencies. Both scripting runtimes run entirely in-process.

Tradeoffs

  • No plugin framework. NGINX provides scripting hooks, not a structured plugin system with a marketplace. You’re writing NGINX config plus inline scripts.
  • Two overlapping scripting systems. The choice between Lua and njs is confusing, and guidance from NGINX on when to use which is limited.
  • njs is not Node.js. Developers expecting npm compatibility, full async/ await, or standard library access are consistently disappointed.
  • Dynamic module compilation must match the exact NGINX version — a pain point similar to Tyk’s Go plugin compatibility issue.
  • No built-in plugin distribution mechanism. OpenResty has opm (a package manager for pure Lua libraries), but there is no unified marketplace.

Who Should Choose NGINX Extensions

Teams with existing NGINX infrastructure that need lightweight scripting for header manipulation, routing, or simple auth checks — not teams building a full programmable API layer.

See the NGINX vs. Zuplo comparison for a platform-level evaluation.

Interpreted Go: Traefik

Traefik takes a unique approach: plugins are written in Go source code but are not compiled. Instead, they’re interpreted at runtime by Yaegi, a Go interpreter embedded in Traefik.

How Traefik Plugins Work

You write a standard Go module with a go.mod and .go files, host the source in a public Git repository, and reference it in Traefik’s configuration. At startup, Traefik downloads the source code and Yaegi interprets it — no build toolchain, no binary compatibility issues, no .so files.

Traefik provides a centralized Plugin Catalog with 100+ community plugins. Developers configure plugins by referencing the module name and version; Traefik fetches the source automatically.

Strengths

  • Zero compilation. The simplest developer experience for getting a custom plugin running. No Bazel, no go build -buildmode=plugin, no version pinning.
  • Go is widely known. The developer pool for Go far exceeds Lua, making hiring and onboarding easier.
  • Centralized Plugin Catalog. A searchable, browsable directory of community plugins — something NGINX and Envoy lack.
  • Acceptable performance overhead. The interpretation overhead is generally modest for middleware workloads like header manipulation and request transformation.

Tradeoffs

  • Yaegi interpreter bugs. The interpreter doesn’t perfectly replicate compiled Go behavior. Developers report issues with struct tag handling, JSON marshaling edge cases, and reflection behavior that works in go test but fails in Yaegi.
  • No CGO or unsafe packages. Plugins cannot use any library that depends on C code, which excludes many Go packages (SQLite bindings, certain crypto libraries).
  • Public source required for the Plugin Catalog. Proprietary middleware requires workarounds to run as a local plugin.
  • No hot-reload. Plugin changes require a Traefik restart.
  • Go only. Unlike Envoy (WASM multi-language) or Tyk (gRPC any-language), Traefik offers no escape hatch for non-Go teams.

Who Should Choose Traefik Plugins

Teams that use Go, want the simplest possible plugin development workflow, and don’t need the scale or ecosystem depth of Kong or Envoy.

See the Traefik vs. Zuplo comparison for more.

TypeScript-First: Zuplo

Zuplo takes a fundamentally different approach to gateway extensibility. Instead of a plugin system bolted onto a proxy, every extension point — inbound policies, outbound policies, and request handlers — is a standard TypeScript function that uses web-standard Request and Response types.

How Zuplo’s Policy System Works

Every request flows through a pipeline: inbound policies → request handler → outbound policies. Each policy is a TypeScript function with a typed signature:

TypeScripttypescript
import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime";

interface PolicyOptions {
  headerName: string;
}

export default async function (
  request: ZuploRequest,
  context: ZuploContext,
  options: PolicyOptions,
  policyName: string,
): Promise<ZuploRequest | Response> {
  const value = request.headers.get(options.headerName);
  if (!value) {
    return HttpProblems.badRequest(request, context, {
      detail: `Missing required header '${options.headerName}'.`,
    });
  }

  context.custom.tenantId = value;
  return request;
}

Return a ZuploRequest to continue the pipeline. Return a Response to short-circuit — the remaining policies and handler are skipped. Outbound policies follow the same pattern, receiving and returning a Response.

Policies are declared in config/policies.json and attached to routes in your OpenAPI-format route configuration. You can freely mix Zuplo’s built-in policies (authentication, rate limiting, validation, caching) with your own custom code.

For a detailed walkthrough, see the Zuplo docs on custom code patterns and custom inbound policies.

What Makes This Different

Several properties distinguish this model from every other architecture in this comparison:

TypeScript with web-standard APIs. ZuploRequest extends the standard Request type. Response, Headers, fetch — these are the same APIs used in Cloudflare Workers, Deno, and modern browsers. If you know JavaScript, you already know the API surface.

npm ecosystem access. You can bundle and import any browser-compatible npm package into your policies. Need Zod for schema validation? A custom JWT library? An SDK for a third-party service? Bundle it with a tool like tsdown, import it, and deploy. No Lua module registry, no Go binary compatibility, no WASM compilation step.

Git-based atomic deployment. Your policies, handlers, and route configuration live in the same Git repository. A git push triggers an atomic deployment — code and config ship together. Every branch gets its own preview environment with a unique URL, so you can test policy changes in isolation before merging to production.

Full type safety. TypeScript provides compile-time checking for policy options, request types, handler return values, and everything in between. This is a meaningful advantage over Lua (no type system), Go plugins (types exist but must match at the ABI level), and WASM (type information is lost at the WASM boundary).

AI Gateway and MCP Gateway on the same programming model. Zuplo’s AI Gateway applies the same policy pipeline to LLM traffic — authentication, rate limiting, semantic caching, and prompt injection protection are all policies configured the same way as any other route. The MCP Gateway turns your existing API routes into MCP tools that AI agents can discover and invoke, with the full policy pipeline enforced on every tool call. In contrast, most OSS gateways require a separate product or sidecar for AI traffic governance.

Strengths

  • Lowest learning curve for most teams. TypeScript is the most widely used typed language. Your existing frontend and backend developers can write gateway policies without learning a new language.
  • Fastest iteration cycle. Write a policy, push to a branch, get a preview URL in seconds. No compilation, no restart, no binary packaging.
  • Full request/response control. Inbound policies can modify any aspect of the request or short-circuit with a custom response. Outbound policies can transform response headers and body. Custom handlers give you complete control over the response.
  • Built-in policies for common use cases. Authentication, rate limiting, CORS, request validation, response caching, and dozens more are available out of the box — no plugin installation required.

Tradeoffs

  • Primarily managed. Zuplo is most commonly deployed as a fully managed platform — either on the global edge network (300+ locations) or as a managed dedicated instance on your cloud provider. A self-hosted option on Kubernetes is also available for teams that require on-premises deployment.
  • Not a drop-in Lua/Go replacement. If you have hundreds of existing Kong Lua plugins or Tyk Go plugins, migration requires rewriting them in TypeScript. The upside is that TypeScript policies are typically shorter and easier to maintain than their Lua equivalents.
  • npm packages must be browser-compatible. Zuplo doesn’t run Node.js — packages that depend on fs, child_process, or other Node-specific APIs won’t work. Most modern npm packages are browser-compatible, but legacy packages may need alternatives.

How to Choose: A Decision Framework

The right plugin architecture depends on your team, not just your gateway. Here are the factors that matter most:

Hiring Market and Team Skills

The language your gateway requires for extensions directly impacts how quickly you can hire, onboard, and retain the engineers who maintain your API layer.

  • TypeScript/JavaScript — the largest developer pool by a wide margin. If your team already writes React, Next.js, or Node.js, they can write Zuplo policies on day one.
  • Go — large and growing developer pool. Fits naturally with Tyk and Traefik if your backend services are in Go.
  • Lua — small, specialist pool. Hiring for Kong or APISIX customization often means training existing engineers rather than hiring Lua developers.
  • C++/Rust (for WASM) — relevant for Envoy native filters. WASM via Rust is powerful but narrows your hiring pool significantly.

Debug and Observability Story

When a policy misbehaves in production, how quickly can you find the problem?

  • TypeScript (Zuplo) — standard console.log / structured logging, source maps, familiar error stack traces.
  • Go (Tyk, Traefik) — Go’s standard tooling (pprof, delve) works for native plugins. Traefik’s interpreted Go loses some debugger integration.
  • Lua (Kong, APISIX) — Lua debugging tools exist but are less mature. Print debugging is common. OpenResty’s error log is your primary diagnostic tool.
  • WASM (Envoy) — the weakest debugging story. WASM extensions are opaque to host debuggers. Error messages from the WASM runtime can be cryptic.

Deployment Model

How plugin changes get from a developer’s machine to production varies dramatically:

  • Zuplogit push triggers atomic deployment. Code and config ship together. Branch-based preview environments for testing.
  • Kong — plugin config changes propagate via database. New plugin code requires kong reload. Packaging via LuaRocks.
  • APISIX — config changes propagate via etcd (milliseconds). Lua plugin hot-reload without restart.
  • Tyk — ZIP bundles uploaded to a bundle server. Go plugins must be recompiled for each gateway version.
  • Envoy — WASM modules delivered via control plane. Native filters compiled into custom binary.
  • Traefik — source fetched from Git at startup. Changes require restart.

AI-Ready Extensibility

If your API layer handles LLM traffic, agent-to-agent communication, or MCP tool calls, your gateway’s extensibility model matters for AI workloads too:

  • Zuplo — AI Gateway and MCP Gateway are policies in the same pipeline. Semantic caching, prompt injection protection, and spend management compose with your existing auth and rate limiting policies.
  • Kong — AI Gateway shipped as a plugin. Works within the Lua ecosystem but arrived later than core gateway features.
  • Envoy, APISIX, Tyk, NGINX, Traefik — no native AI gateway layer. Governing LLM traffic requires a separate product, sidecar, or custom plugin development.

Putting It Together

There is no universally “best” plugin architecture — but there is a best one for your team’s language expertise, deployment model, and growth trajectory.

If your team writes TypeScript or JavaScript and you want the fastest path from custom policy to production, Zuplo’s model eliminates the packaging, compilation, and distribution overhead that every other architecture requires. Start building with Zuplo for free to see TypeScript-first gateway extensibility in action.

If your team writes Go and you need self-hosted control, Tyk (for native performance) or Traefik (for zero-compilation simplicity) are strong fits.

If your team writes Lua or has deep OpenResty experience, Kong and APISIX offer the most mature ecosystems and the largest community of pre-built plugins.

If you’re running Envoy or Istio for service mesh and need sandboxed extensibility, WASM filters are the native path forward — with the caveat that WASM tooling and debugging are still maturing.

Whatever you choose, the goal is the same: custom logic that ships quickly, debugs easily, and doesn’t become a maintenance bottleneck as your API surface grows. The architectures differ in how they get you there.

For a broader comparison of API gateway platforms beyond extensibility, see Best API Gateways in 2026 and API Gateway Patterns.