graphql-ws
6.4.1 · 15 Oct 2026MIT10.6M / weekRepositoryHomepagenpmAtom feed
122 stable releases, newest first, by release line. Notes are quoted as the maintainers published them.
- 6.x · latest line
6.4.1
15 Oct 2026Use the
jsonMessageReplacerwhen automatically responding to a ping with a pong, on both the client and the server
6.4.0
13 Oct 2026Add an
isProdoption to thews,uWebSockets.jsand@fastify/websocketuse-server helpers, matching the existingcrosswsoption. It controls whether internal error messages are masked withInternal server errorbefore reaching the client, defaulting to theNODE_ENV === 'production'check as before. This lets apps running in production keep reportingonConnecterror messages to the client by opting out, or mask eagerly in non-production environments.Fix the client closing an in-use socket when many subscriptions complete at once and a new one subscribes within the
lazyCloseTimeoutEvery completing subscription scheduled its own lazy close timeout, but only the last one got cleared on the next connect. The client now keeps a single lazy close timeout and checks for active subscriptions before closing.
Avoid an unhandled rejection when a subscription is disposed before the connection is acknowledged and the socket closes before the subscription releases its connection lock (for example, when the client is disposed while connecting and the WebSocket implementation delivers the close event on a microtask).
6.3.0
24 Sep 2026Add
onPingandonPongserver callbacks toServerOptions, similar toonConnectandonDisconnect. The callbacks receive the connectionContextas the first argument and the ping/pongpayloadas the second, allowing apps to log or react to subprotocol-level pings with access to connection state. The automatic pong reply is preserved when using the server-levelonPingcallback; the low-level websocketonPinglistener still disables the automatic reply for full manual control.Fix the client leaking memory per operation on long-living connections
Allow operation IDs that match properties inherited from
Object.prototype.
6.2.2
16 Sep 2026Support crossws 0.4 by selecting the GraphQL WebSocket subprotocol during the upgrade handshake. Preserve automatic protocol negotiation in the legacy crossws 0.3 Node and uWebSockets adapters.
Fix the CrossWS adapter ignoring socket closes issued from
server.openedmakeHooksonly registered the peer in the clients map afterserver.openedreturned, whilesend/closeno-op'd unless the peer was already in that map. A protocol-mismatch close (and any other close from insideopened) was therefore dropped, the WebSocket stayed open, and noConnectionAckwas ever sent because the message handler was never installed.
6.2.1
6 Aug 2026Fix typings for WebSocketServer
6.2.0
24 Jul 2026Add
parseoption for custom GraphQL parsing
6.1.1
22 Jul 2026Disposing of a client event listener twice no longer removes an unrelated listener
The unsubscribe function returned by
client.onspliced atindexOf(listener)without checking for-1, so removing an already-removed listener wouldsplice(-1, 1)and silently drop the most recently registered listener of the same event. This happens in practice without any double-dispose by the user: emits iterate over a copy of the listeners, so a one-shot internal listener that already unlistened itself during a nested emit (e.g. whenclient.terminate()is called from within aclosed/errorlistener) is re-invoked from the copy and unlistens again, knocking out registeredclosed/errorlisteners.
6.1.0
10 Jul 2026Add support for
graphql@17
6.0.8
26 Mar 2026Fix the server sending a
Completemessage after anErrormessage for subscriptions.Previously, when a subscription's async iterable threw an error, the server would send:
{"id":"1","type":"error","payload":[{"message":"..."}]} {"id":"1","type":"complete"}Per the protocol spec:
Error: This message terminates the operation and no further messages will be sent.
Complete (Server → Client): If the server dispatched the
Errormessage relative to the originalSubscribemessage, noCompletemessage will be emitted.The server now correctly sends only the
Errormessage:{"id":"1","type":"error","payload":[{"message":"..."}]}Clients that correctly follow the spec should be unaffected, as they are expected to ignore messages for operations they consider already completed.
6.0.7
27 Jan 2026Remove uWebSockets.js from peer dependencies in package.json
It does not exist on NPM anymore and could lead to weird behavior when installing dependencies with
npm. Nothing else changes, usinggraphql-wswith uWebSockets.js still works.
6.0.6
14 Jul 2025Fix building issues causing CJS type definitions referencing ESM modules
6.0.4
14 Feb 2025Use Math.pow for retry delay calculation
6.0.3
30 Jan 2025Drop
ExecutionPatchResultandFormattedExecutionPatchResulttypesNeither of the types are officially supported (yet) and the future versions of graphql-js adding support for stream/defer will a different signature for the incremental execution result.
6.0.2
21 Jan 2025FormattedExecutionResult errors field returns GraphQLFormattedError
6.0.1
16 Jan 2025- removed
Remove exports for CommonJS for Deno exports in package.json
Define exports for CommonJS TypeScript definitions in package.json
6.0.0
15 Jan 2025- breaking
@fastify/websocket WebSocket in the context extra has been renamed from
connectiontosocket - removed
Drop support for
wsv7wsv7 has been deprecated. Please upgrade and use v8. - removed
Drop support for deprecated
fastify-websocketfastify-websockethas been deprecated since v4.3.0.. Please upgrade and use@fastify/websocket.
Major Changes
- breaking
@fastify/websocket WebSocket in the context extra has been renamed from
connectiontosocket
Migrating from v5 to v6
import { makeHandler } from 'graphql-ws/use/@fastify/websocket'; makeHandler({ schema(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, context(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onConnect(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onDisconnect(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onClose(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onSubscribe(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onOperation(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onError(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onNext(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, onComplete(ctx) { - const websocket = ctx.extra.connection; + const websocket = ctx.extra.socket; }, });- removed
Drop support for
wsv7wsv7 has been deprecated. Please upgrade and use v8. - removed
Drop support for deprecated
fastify-websocketfastify-websockethas been deprecated since v4.3.0.. Please upgrade and use@fastify/websocket. The
/lib/part from imports has been removed, for examplegraphql-ws/lib/use/wsbecomesgraphql-ws/use/ws
Migrating from v5 to v6
Simply remove the
/lib/part from your graphql-ws imports that use a handler.ws
- import { useServer } from 'graphql-ws/lib/use/ws'; + import { useServer } from 'graphql-ws/use/ws';uWebSockets.js
- import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets'; + import { makeBehavior } from 'graphql-ws/use/uWebSockets';@fastify/websocket
- import { makeHandler } from 'graphql-ws/lib/use/@fastify/websocket'; + import { makeHandler } from 'graphql-ws/use/@fastify/websocket';Bun
- import { handleProtocols, makeHandler } from 'graphql-ws/lib/use/bun'; + import { handleProtocols, makeHandler } from 'graphql-ws/use/bun';Deno
- import { makeHandler } from 'https://esm.sh/graphql-ws/lib/use/deno'; + import { makeHandler } from 'https://esm.sh/graphql-ws/use/deno';ErrorMessageuses andonErrorreturnsGraphQLFormattedError(instead ofGraphQLError)Least supported Node version is v20
Node v10 has been deprecated for years now. There is no reason to support it. Bumping the engine to the current LTS (v20) also allows the code to be leaner and use less polyfills.
Least supported
graphqlpeer dependency is ^15.10.1 and ^16Users are advised to use the latest of
graphqlbecause of various improvements in performance and security.NextMessageuses andonNextreturnsFormattedExecutionResult(instead ofExecutionResult)schema,context,onSubscribe,onOperation,onError,onNextandonCompletehooks don't have the full accompanying message anymore, only the ID and the relevant part from the messageThere is really no need to pass the full
SubscribeMessageto theonSubscribehook. The only relevant parts from the message are theidand thepayload, thetypeis useless since the hook inherently has it (onNextisnexttype,onErroriserrortype, etc).The actual techincal reason for not having the full message is to avoid serialising results and errors twice. Both
onNextandonErrorallow the user to augment the result and return it to be used instead.onNextoriginally had theNextMessageargument which already has theFormattedExecutionResult, andonErrororiginally had theErrorMessageargument which already has theGraphQLFormattedError, and they both also returnedFormattedExecutionResultandGraphQLFormattedErrorrespectivelly - meaning, if the user serialised the results - the serialisation would happen twice.Additionally, the
onOperation,onError,onNextandonCompletenow have thepayloadwhich is theSubscribeMessage.payload(SubscribePayload) for easier access to the original query as well as execution params extensions.
Migrating from v5 to v6
schemaimport { ExecutionArgs } from 'graphql'; import { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - schema(ctx, message, argsWithoutSchema: Omit<ExecutionArgs, 'schema'>) { - const messageId = message.id; - const messagePayload: SubscribePayload = message.payload; - }, + schema(ctx, id, payload) { + const messageId = id; + const messagePayload: SubscribePayload = payload; + }, };contextimport { ExecutionArgs } from 'graphql'; import { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - context(ctx, message, args: ExecutionArgs) { - const messageId = message.id; - const messagePayload: SubscribePayload = message.payload; - }, + context(ctx, id, payload, args: ExecutionArgs) { + const messageId = id; + const messagePayload: SubscribePayload = payload; + }, };onSubscribeimport { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - onSubscribe(ctx, message) { - const messageId = message.id; - const messagePayload: SubscribePayload = message.payload; - }, + onSubscribe(ctx, id, payload) { + const messageId = id; + const messagePayload: SubscribePayload = payload; + }, };onOperationThe
SubscribeMessage.payloadis not useful here at all, thepayloadhas been parsed to ready-to-use graphql execution args and should be used instead.import { ExecutionArgs } from 'graphql'; import { ServerOptions, SubscribePayload, OperationResult } from 'graphql-ws'; const opts: ServerOptions = { - onOperation(ctx, message, args: ExecutionArgs, result: OperationResult) { - const messageId = message.id; - const messagePayload: SubscribePayload = message.payload; - }, + onOperation(ctx, id, payload, args: ExecutionArgs, result: OperationResult) { + const messageId = id; + const messagePayload: SubscribePayload = payload; + }, };onErrorThe
ErrorMessage.payload(GraphQLFormattedError) is not useful here at all, the user has access toGraphQLErrorthat are true instances of the error containing object references tooriginalErrors and other properties. The user can always convert and returnGraphQLFormattedErrorby using the.toJSON()method.import { GraphQLError, GraphQLFormattedError } from 'graphql'; import { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - onError(ctx, message, errors) { - const messageId = message.id; - const graphqlErrors: readonly GraphQLError[] = errors; - const errorMessagePayload: readonly GraphQLFormattedError[] = message.payload; - }, + onError(ctx, id, payload, errors) { + const messageId = id; + const graphqlErrors: readonly GraphQLError[] = errors; + const subscribeMessagePayload: SubscribePayload = payload; + const errorMessagePayload: readonly GraphQLFormattedError[] = errors.map((e) => e.toJSON()); + }, };onNextThe
NextMessage.payload(FormattedExecutionResult) is not useful here at all, the user has access toExecutionResultthat contains actual object references to error instances. The user can always convert and returnFormattedExecutionResultby serialising the errors withGraphQLError.toJSON()method.import { ExecutionArgs, ExecutionResult, FormattedExecutionResult } from 'graphql'; import { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - onNext(ctx, message, args: ExecutionArgs, result: ExecutionResult) { - const messageId = message.id; - const nextMessagePayload: FormattedExecutionResult = message.payload; - }, + onNext(ctx, id, payload, args: ExecutionArgs, result: ExecutionResult) { + const messageId = id; + const subscribeMessagePayload: SubscribePayload = payload; + const nextMessagePayload: FormattedExecutionResult = { ...result, errors: result.errors?.map((e) => e.toJSON()) }; + }, };onCompleteimport { ServerOptions, SubscribePayload } from 'graphql-ws'; const opts: ServerOptions = { - onComplete(ctx, message) { - const messageId = message.id; - }, + onComplete(ctx, id, payload) { + const messageId = id; + const subscribeMessagePayload: SubscribePayload = payload; + }, };Errors thrown from subscription iterables will be caught and reported through the
ErrorMessageCompared to the behaviour before, which terminated the whole WebSocket connection - those errors are now gracefully reported and terminate only the specific subscription that threw the error.
There's been an editorial change in the GraphQL Spec suggesting this being the correct approach.
Also, if you'd like to get involved and ideally drop your opinion about whether iterable errors should be reported as errors or
ExecutionResults witherrorsfield set, please read more here.
Migrating from v5 to v6
If you had used the suggested "ws server usage with custom subscribe method that gracefully handles thrown errors" recipe, you can simply remove it since this behaviour is now baked in.
import { subscribe } from 'graphql'; import { useServer } from 'graphql-ws/use/ws'; import { WebSocketServer } from 'ws'; // yarn add ws const wsServer = new WebSocketServer({ port: 4000, path: '/graphql', }); useServer( { schema, - async subscribe(...args) { - const result = await subscribe(...args); - if ('next' in result) { - // is an async iterable, augment the next method to handle thrown errors - const originalNext = result.next; - result.next = async () => { - try { - return await originalNext(); - } catch (err) { - // gracefully handle the error thrown from the next method - return { value: { errors: [err] } }; - } - }; - } - return result; - }, }, wsServer, );- removed
Remove deprecated
isMessage, usevalidateMessageinstead
Migrating from v5 to v6
Replace all ocurrances of
isMessagewithvalidateMessage. Note thatvalidateMessagethrows if the message is not valid, compared withisMessagethat simply returned true/false.- import { isMessage } from 'graphql-ws'; + import { validateMessage } from 'graphql-ws'; function isGraphQLWSMessage(val) { - return isMessage(val); + try { + validateMessage(val); + return true; + } catch { + return false; + } }- removed
Removed deprecated
isFatalConnectionProblem, useshouldRetryinstead
Migrating from v5 to v6
Replace all ocurrances of
isFatalConnectionProblemwithshouldRetry. Note that the result is inverted, where you returnedfalseinisFatalConnectionProblemyou should returntrueinshouldRetry.import { createClient } from 'graphql-ws'; const client = createClient({ url: 'ws://localhost:4000/graphql', - isFatalConnectionProblem: () => false, + shouldRetry: () => true, });Minor Changes
Client is truly zero-dependency, not even a peer dependency on
graphqlIn non-browser environments, you can use only the client and not even depend on
graphqlby importing fromgraphql-ws/client.import { createClient } from 'graphql-ws/client'; const client = createClient({ url: 'ws://localhost:4000/graphql', });Note that, in browser envirments (and of course having your bundler use the
browserpackage.json field), you don't have to import fromgraphql-ws/client- simply importing fromgraphql-wswill only have thecreateClientavailable.Define optional peer dependencies and least supported versions
Using the
peerDependenciesin combination withpeerDependenciesMetaconfiguration inpackage.json.
- 5.x
5.16.2
10 Jan 2025Reading the note…
5.16.1
8 Jan 2025Reading the note…
5.16.0
27 Mar 2024Reading the note…
5.15.0
12 Feb 2024Reading the note…
5.14.3
20 Dec 2023Reading the note…
5.14.2
23 Oct 2023Reading the note…
5.14.1
28 Sep 2023Reading the note…
5.14.0
22 Jun 2023Reading the note…
5.13.1
15 May 2023Reading the note…
5.13.0
12 May 2023Reading the note…
5.12.1
31 Mar 2023Reading the note…
5.12.0
6 Mar 2023Reading the note…
5.11.3
1 Feb 2023Reading the note…
5.11.2
21 Sep 2022Reading the note…
5.11.1
16 Sep 2022Reading the note…
5.11.0
16 Sep 2022Reading the note…
5.10.2
12 Sep 2022Reading the note…
5.10.1
19 Aug 2022Reading the note…
5.10.0
9 Aug 2022Reading the note…
5.9.1
1 Jul 2022Reading the note…
5.9.0
9 Jun 2022Reading the note…
5.8.2
12 May 2022Reading the note…
5.8.1
25 Apr 2022Reading the note…
5.8.0
25 Apr 2022Reading the note…
5.7.0
7 Apr 2022Reading the note…
5.6.4
24 Mar 2022Reading the note…
5.6.3
13 Mar 2022Reading the note…
5.6.2
23 Feb 2022Reading the note…
5.6.1
21 Feb 2022Reading the note…
5.6.0
19 Feb 2022Reading the note…
5.5.5
29 Oct 2021Reading the note…
5.5.4
27 Oct 2021Reading the note…
5.5.3
20 Oct 2021Reading the note…