Extensibility ยท Custom modules

GraphQL Router Middleware in Go

Add custom logic to the router in Go (auth, caching, rate limiting) without running a separate proxy or forking the codebase.

Same binary. No extra hop. Every Go tool works on it.

Request lifecycle hooks

Provisionerrouter startCleanerrouter shutdownRouterOnRequestHandlerearliestCosmo Routerin-process Go modulesRouterMiddlewareHandlerEnginePreOriginHandlerbefore subgraphSubgraphfetchEnginePostOriginHandlerafter subgraphSame process, same binary; hooks run in-process with full request context

Available onFreeProEnterprise

The problem

Why teams bolt on proxies, scripting, or forks

Federated GraphQL needs gateway logic that talks to the rest of your platform. That rarely fits in a single hop, a sandboxed script, or a one-off fork.

External proxies add a network hop

A sidecar or Envoy filter in front of the router handles the custom logic, at the cost of an extra hop, another process to run, and metrics that live in two places.

Scripting languages limit what you can reach for

Lua, JavaScript, or sandboxed WASM filters cover basic shape-shifting, but they can't call your company's Go client library, your internal gRPC service, or the cache SDK your platform team already ships.

Forked gateways drift

Copying the router source to add a few hooks means inheriting every merge conflict forever. Upstream fixes stop being free.

Our solution

Add custom gateway logic without running a second process

Custom modules are Go packages linked into the router binary. Your code runs in the same process as routing and federation, so you can reuse internal clients and observability without adding another network hop or maintaining a fork.

Each interface hooks into a specific point

  1. One module can implement several interfaces at once, and multiple modules can coexist; set priority when their relative order matters.

  2. RouterOnRequestHandler runs as early as possible, before built-in auth and parsing.

  3. RouterMiddlewareHandler runs after the operation is parsed, with access to the GraphQL operation and query plan stats.

  4. EnginePreOriginHandler and EnginePostOriginHandler wrap each subgraph fetch and response.

  5. Provisioner and Cleaner run at router start and shutdown for module lifecycle.

  6. At request time, handlers fire in-process in the same goroutine as the request, with access to the full request context.

One binary. Native performance. Your libraries, in-process.

Custom modules

Before & After

Before CosmoWith Cosmo
External proxy layer for custom logicIn-process Go module, zero network hop
Scripting language with limited reachFull Go ecosystem and compiled performance
A single "on request" hook, workarounds for the restSix distinct hooks for precise lifecycle control
Hard to test: proxy plus router plus test harnessPlain Go: go test, pprof, delve all apply

Router extensibility

The six interfaces

InterfaceWhen it runs
RouterOnRequestHandlerEarliest intercept, before auth and parsing
RouterMiddlewareHandlerAfter the operation is known, with query plan stats
EnginePreOriginHandlerBefore each subgraph fetch
EnginePostOriginHandlerAfter each subgraph response
ProvisionerModule setup on router start
CleanerModule teardown on router shutdown

What a module can see

  • GraphQL operation name, type, hash, content
  • Query plan stats (subgraph fetch count, etc.)
  • Request context and per-request store
  • Subgraph name, ID, and URL (for pre/post origin hooks)
  • Authentication info (readable and modifiable)

How custom Go modules work

01
Compose behavior like middleware, but native Go.

Implement interfaces

Pick the hook point(s) needed. A module can implement more than one interface.

02
Config lives in router YAML, not scattered env vars.

Register the module

Register with the router. Set priority if order relative to other modules matters. Map YAML config values to struct fields with tags.

03
Ship one artifact, no sidecar fleet.

Build the router binary

Compile the router with your modules included. Output is a single binary.

04
pprof and delve see your code.

Handlers fire in-process

At runtime, each hook fires at its point in the request lifecycle in the same goroutine as the request, with access to the full request context.

Use cases

Patterns teams ship first

Auth, cache, cost controls, and headers at the gateway, without subgraph changes.

Custom authentication

Earliest stage

RouterOnRequestHandler runs before the router's built-in auth. Extract credentials, validate against the internal system, either let the request continue or return an early error.

Gateway-level caching

Pre / Post origin

EnginePreOriginHandler checks cache using the operation hash and query plan stats; on a hit it returns early and skips the subgraph fetch. EnginePostOriginHandler populates the cache after a miss.

Per-tenant rate limiting

After parse

RouterMiddlewareHandler reads operation details and QueryPlanStats, estimates cost based on subgraph fetch count, and enforces per-tenant budgets.

Destination-aware header transformation

Per subgraph

EnginePreOriginHandler reads ctx.ActiveSubgraph() and adds or rewrites headers on the outgoing request. No subgraph code changes.

Request header operations cover declarative cases; modules cover dynamic logic.

Requirements & operations

Build-time

Requirements

  • Go development environment
  • Router source access for building the custom binary
  • Familiarity with Go interfaces and the standard http.Handler pattern

Ops notes

  • Modules compile in; plan releases when router versions bump.
  • Use priorities to keep cross-module ordering predictable.

Prefer YAML and header rules when they suffice; reach for Go when you need real code paths.

Extend the router in Go today

Six hooks, one binary, zero extra hop. Free tier included.

FAQ

Custom Go modules

More detail in the custom modules documentation.

Examples and repos: router-examples, custom module with tests, custom JWT sample, and the module system ADR.