GraphQL subscriptions
The GraphQL transport serves subscriptions over graphql-transport-ws on /graphql. It carries subscriptions only, not the item CRUD operations that the item protocol offers. It is configured by the WEBSOCKETS_GRAPHQL_* family (see Manage configuration).
A GraphQL subscription receives no initial snapshot. It receives the change events only, in the shape of the selection set.
Connecting
Section titled “Connecting”Open a WebSocket to /graphql with the graphql-transport-ws subprotocol, then send connection_init. Under handshake or public, the access token travels in the init payload:
{ "type": "connection_init", "payload": { "access_token": "<token>" } }Under strict, present the token as an Authorization: Bearer header at the upgrade and send connection_init with no token in the payload. The server answers with connection_ack.
The subscription field
Section titled “The subscription field”Each subscribable collection exposes a <collection>_mutated field on the Subscription root. It takes an optional event argument (create, update, or delete) and returns the changed record.
subscription { articles_mutated(event: create) { event data { id title } }}The selection set chooses the fields to receive. event is the change kind. data is the changed item, selected field by field. Omit the event argument to receive create and update.
Standard GraphQL aliases and fragments work, and multiple _mutated subscriptions can run on one connection, each as its own operation.
Delete events
Section titled “Delete events”A delete is delivered only when event: delete is requested. Because a deleted item cannot be read back, the delete result carries the key and a null data:
subscription { articles_mutated(event: delete) { event key data { id } }}A delivered delete has the exact shape { "event": "delete", "key": "<id>", "data": null }. The key is the deleted primary key as a string. The delete feed has eligibility rules described under the delete feed. An ineligible delete subscription returns a FORBIDDEN error.
The GraphQL subscription surface is scoped to items, so a <collection>_mutated field exists for item collections only. The dashboards, notifications, operations, panels, and shares scopes that the item protocol can observe are not exposed here. Use the item protocol for those.
Gates and redaction
Section titled “Gates and redaction”The GraphQL WebSocket surface enforces the same guards as the GraphQL HTTP endpoint:
- Query token limit. A subscription document larger than
GRAPHQL_QUERY_TOKEN_LIMITis rejected. - Introspection. When
GRAPHQL_INTROSPECTIONis off, an introspecting document is rejected. - Error redaction. Internal error detail is redacted from the error sent to the client.
Renewal
Section titled “Renewal”GraphQL has no in-band reauthentication. In public mode, expiry drops the connection to anonymous access without a notice frame. Reconnect with a fresh token and resubscribe to restore authenticated access. In handshake and strict, expiry closes the connection and requires a reconnect. See the authentication table for the full mode-specific outcomes.
Close codes
Section titled “Close codes”The GraphQL transport uses the graphql-transport-ws close codes. The ones to expect:
4400— a malformed frame, or a frame that is not valid for the protocol state.4401— asubscribewas sent before the connection was acknowledged.4403— forbidden: an authentication failure atconnection_init, or a token supplied understrict.4408— connection-initialisation timeout: noconnection_initarrived within the deadline. This applies underpublicandstrict. Underhandshakethegraphql-transport-wsinit timer is disabled and the base authentication deadline applies instead, so a handshake connection that never authenticates closes on that deadline without this protocol code.4409— asubscribereused an operationidthat is already active on the connection.4429— more than oneconnection_initwas sent.1009— an outbound frame would exceed the 1 MiB frame bound.1013— try again later, when capacity, a rate limit, or the pending-command limit is exceeded.4500— an internal error while delivering a subscription.
The 4400, 4403, 1013, and 4500 closes come from CairnCMS. The 4401, 4408, 4409, and 4429 closes come from the graphql-transport-ws protocol layer, and 1009 from the shared outbound-frame guard.
On any close, reconnect and resubscribe, then reread current state. A subscription is ended cleanly with the protocol’s complete message.
Where to go next
Section titled “Where to go next”- Subscription authorization — permission checks, row-level filters, and delete-feed eligibility.
- Authentication —
connection_init, strict upgrades, and token renewal. - GraphQL API — queries, mutations, schema exposure, and HTTP GraphQL behavior.
- Reliability — deployment, limits, and recovery.