---
title: "API Gateway Plugin Architectures Compared (2026): Lua, Go, WASM, and TypeScript"
description: "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."
canonicalUrl: "https://zuplo.com/learning-center/api-gateway-plugin-architectures-compared"
pageType: "learning-center"
authors: "nate"
tags: "API Gateway"
image: "https://zuplo.com/og?text=API%20Gateway%20Plugin%20Architectures%20Compared%20(2026)"
---
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](https://openresty.org/en/),
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](/api-gateways/kong-alternative-zuplo) and
[APISIX vs. Zuplo](/api-gateways/apisix-alternative-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](/api-gateways/tyk-api-management-alternative-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](https://github.com/proxy-wasm/spec) 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](/api-gateways/envoy-alternative-zuplo) comparison and
[Envoy as Your API Gateway](/learning-center/envoy-as-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](/api-gateways/nginx-alternative-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](https://github.com/traefik/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](https://plugins.traefik.io/plugins) 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](/api-gateways/traefik-alternative-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:

```typescript
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](/docs/articles/custom-code-patterns) and
[custom inbound policies](/docs/policies/custom-code-inbound).

### 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](/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](/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](https://zuplo.com/docs/dedicated/overview) instance on
  your cloud provider. A
  [self-hosted option](https://zuplo.com/docs/self-hosted/overview) 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:

- **Zuplo** — `git 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](https://portal.zuplo.com) 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](/learning-center/best-api-gateways-2026) and
[API Gateway Patterns](/learning-center/api-gateway-patterns).