Skip to main content
subscription

Subscription

subscription OperationName($var: Type) { eventField(filter: $var) { id payload } }

A GraphQL subscription establishes a long-lived connection over which the server pushes events to the client as they occur. Subscriptions are executed once per event – when a subscribed event fires, the server runs the subscription selection set against the event payload and pushes the result. The dominant transport is WebSocket using the graphql-ws protocol (RFC: github.com/enisdenjo/graphql-ws). Server-Sent Events (SSE) is also used for unidirectional push.

How it works

Subscriptions enable real-time data in GraphQL. Unlike queries and mutations which are request-response, subscriptions keep a connection open and deliver a stream of results.

Execution model: when a client subscribes, the server registers an event handler for the specified topic. Each time the event fires (a new message, an order status change, a price tick), the server executes the subscription's selection set against the event payload and sends the result to the client. This is fundamentally different from polling – the server initiates the push.

Transport – graphql-ws: the dominant protocol for GraphQL subscriptions over WebSocket. The graphql-ws spec defines message types: connection_init, connection_ack, subscribe, next, error, complete. Each subscription within a single WebSocket connection is identified by a client-chosen string ID, enabling multiplexing.

Transport – SSE: for unidirectional push (server to client only), Server-Sent Events over HTTP work well. The GraphQL over HTTP draft spec defines how to use multipart/mixed responses for incremental delivery. SSE subscriptions work through firewalls and proxies that block WebSocket.

Resolver model: subscription resolvers have two parts – an async iterator (the event source, e.g., a Redis pub/sub channel) and a resolve function (transforms the raw event into the GraphQL result type).

Single root field: the spec (§6.2) requires a subscription operation to have exactly one selection at the root level. Multiple root fields in a subscription are forbidden.

Examples

Basic subscription
subscription OnNewMessage($channelId: ID!) {
  messageSent(channelId: $channelId) {
    id
    text
    sender {
      name
      avatarUrl
    }
    sentAt
  }
}
graphql-ws protocol messages
// Client → Server: initialize connection
{ "type": "connection_init", "payload": { "Authorization": "Bearer <token>" } }

// Server → Client: acknowledge
{ "type": "connection_ack" }

// Client → Server: start subscription
{ "type": "subscribe", "id": "sub_1",
  "payload": { "query": "subscription { orderUpdated(orderId: \"ord_1\") { status } }" } }

// Server → Client: event received
{ "type": "next", "id": "sub_1",
  "payload": { "data": { "orderUpdated": { "status": "SHIPPED" } } } }

// Client → Server: stop subscription
{ "type": "complete", "id": "sub_1" }
Real-time price ticker
subscription PriceTicker($symbol: String!) {
  priceUpdated(symbol: $symbol) {
    symbol
    price
    change
    changePercent
    timestamp
  }
}

Spec Rules

§

A subscription operation MUST have exactly one root field – multiple root fields are forbidden by spec §6.2

§

Subscriptions must use a persistent transport – WebSocket (graphql-ws) or SSE, not plain HTTP

§

Each subscription within a WebSocket connection needs a unique client-chosen ID for multiplexing

§

Subscription resolvers require two parts: an async iterator (event source) and a resolve function

§

Always handle connection_ack timeout – if no ack within N seconds, the connection should be considered failed

§

Never put heavy computation in the subscription resolver – it runs on every event for every subscribed client

See Also