---
title: GraphQL
description: Run a GraphQL endpoint through the gateway with parsing, depth/complexity limits, introspection control, and a built-in GraphiQL IDE.
---

# GraphQL

The gateway ships a **GraphQL-aware layer** that fronts a GraphQL upstream. When enabled it
parses each incoming operation, enforces depth/complexity/introspection policy, serves a real
GraphiQL IDE, and proxies valid queries to your GraphQL backend through the normal routing,
auth, rate-limit, and load-balancing pipeline.

<Callout type="info">
  This is **single-graph** support: the gateway puts a policy and IDE layer in front of one
  GraphQL upstream per endpoint. Multi-subgraph **federation** (schema composition, query
  planning, cross-subgraph entity resolution) is on the roadmap, not yet available.
</Callout>

## How it works

The GraphQL layer runs as a middleware *inside* the request pipeline — after authentication
and rate limiting, before the proxy. For a request to the configured endpoint it will:

- Serve a real **GraphiQL IDE** for browser requests — a `GET` whose `Accept` contains
  `text/html` and which has no `query=` parameter. The IDE is configured to talk to the same
  endpoint.
- Read the operation from a `POST` JSON body (`{"query": "..."}`) or from the `query` URL
  parameter on a `GET`.
- **Parse** the operation with a spec-compliant GraphQL parser and reject malformed queries.
- Enforce **query-depth** and **complexity** limits, and **introspection** policy.
- For a valid operation, hand the request back to the pipeline so it is **proxied to the
  upstream** declared by the matching route — with the gateway's routing, auth, rate limiting,
  and load balancing applied.

Policy rejections return a GraphQL error envelope (`{"errors":[{"message": "..."}]}`) so
GraphQL clients handle them normally.

## Enabling it

Add a `graphql:` block to the gateway config **and** declare a normal route for the endpoint
pointing at your GraphQL upstream. The middleware adds parsing and policy enforcement on top of
that route; the route itself does the routing and proxying.

```yaml
graphql:
  enabled: true
  endpoint: /graphql        # path the GraphQL layer intercepts
  playground: true          # serve GraphiQL on GET /graphql (HTML Accept)
  introspection: true       # allow __schema / __type queries
  max_depth: 15             # reject operations nested deeper than this
  max_complexity: 1000      # reject operations selecting more fields than this

upstreams:
  - name: graphql-backend
    instances:
      - id: graphql-1
        host: 127.0.0.1
        port: 4000

routes:
  - path: /graphql
    methods: [GET, POST]
    upstream: graphql-backend
```

The layer is **disabled by default** (`enabled: false`); existing deployments are unaffected
until you turn it on. If `graphql.enabled` is `true` but no route matches the endpoint path, a
request to it returns the gateway's normal "route not found" response — the route is what
supplies the upstream.

### Configuration reference

| Field            | Default     | Description                                                                 |
| ---------------- | ----------- | --------------------------------------------------------------------------- |
| `enabled`        | `false`     | Turn the GraphQL-aware layer on.                                            |
| `endpoint`       | `/graphql`  | Path the layer intercepts. Other paths pass straight through.              |
| `playground`     | `true`      | Serve the GraphiQL IDE on an HTML `GET` to the endpoint.                   |
| `introspection`  | `true`      | When `false`, operations selecting `__schema`/`__type` are rejected.       |
| `max_depth`      | `15`        | Maximum selection nesting depth (root selection set = depth 1).            |
| `max_complexity` | `1000`      | Maximum number of selected fields (an approximate query cost).            |

## Subscriptions

GraphQL subscriptions are typically delivered over WebSocket (`graphql-ws` /
`graphql-transport-ws`). The WebSocket proxy forwards the `Sec-WebSocket-Protocol` subprotocol,
so a subscription endpoint can be proxied as a normal WebSocket route. See
[WebSocket](/docs/octopus/protocols/websocket).

## Limitations

- **Single-graph only.** No schema composition, query planning, or cross-subgraph entity
  resolution yet — point the endpoint at one GraphQL upstream. (Federation is planned. The
  gateway's existing "federation" machinery is part of [FARP](/docs/octopus/concepts/farp) service
  discovery, not GraphQL.)
- **Complexity is a field count.** `max_complexity` approximates cost by counting selected
  fields; it is not type-weighted.
- **Fragment spreads aren't expanded** for depth/complexity analysis (named fragments are not
  resolved against a schema at this stage); inline fragments are analyzed at their enclosing
  level.
- **No query batching or persisted queries (APQ)** yet.
- **Only the exact endpoint path is intercepted.** Subpaths (e.g. `/graphql/foo`) pass through
  as ordinary HTTP and are routed normally.

See also [Routes](/docs/octopus/configuration/routes), [Routing](/docs/octopus/concepts/routing), and
[HTTP/1.1 & HTTP/2](/docs/octopus/protocols/http).
