Introducing GraphQL.js v17

Yaacov Rydzinski

On behalf of the open-source maintainers and contributors of GraphQL.js, we are excited to announce the availability of GraphQL.js v17!

We encourage users to upgrade to v17. To make that practical, graphql-js.org now includes detailed upgrade guides from v14 to v15, v15 to v16, and v16 to v17. The site also has new content, including full API references for v17 and v16.

If you have migration questions, please reach out in the #graphql-js channel on the GraphQL Discord server, or open an issue if a migration problem needs tracking. If you have corrections or suggestions for the guides, open a pull request.

Why upgrade?

GraphQL.js v17 delivers stronger runtime integration APIs, improved schema correctness and spec alignment, and polish to runtime and tooling APIs. It also includes exciting experimental features, including fragment arguments, an experimental error mode related to future onError behavior, and incremental delivery, for teams that want to help test proposal-backed GraphQL features in production-shaped systems.

Runtime integration APIs

For framework and server authors, v17 introduces a cleaner lower-level execution boundary. validateExecutionArgs() validates and normalizes execution arguments once, and the execute helpers run the matching root selection set: executeRootSelectionSet() for stable single-result execution and experimentalExecuteRootSelectionSet() for incremental delivery. Subscriptions now have their own boundary: validateSubscriptionArgs() validates the subscription-specific shape, createSourceEventStream() creates the source event stream, mapSourceToResponseEvent() maps source events to GraphQL responses, and executeSubscriptionEvent() executes one event. These helpers let hosts customize execution without copying GraphQL.js internals.

For teams operating GraphQL.js servers, v17 adds first-class AbortSignal support and resolver access to cancellation through info.getAbortSignal(). Request timeouts, client disconnects, and downstream APIs that accept AbortSignal can now share one GraphQL.js-supported cancellation path. When execution aborts after producing partial data, AbortedGraphQLExecutionError exposes the abort cause and partial result.

The companion asyncWorkFinished execution hook lets hosts observe when tracked async execution work has settled. That matters for cleanup, tests, tracing, and transports as GraphQL.js sometimes stops producing a response before every async iterator or resolver cleanup task has finished.

v17 also adds the GraphQLHarness shape for customizing the parse, validate, execute, and subscribe phases used by graphql() and graphqlSync(). This intentionally follows the phase model proven by Envelop, bringing the resulting function types into the reference implementation. In particular, GraphQLParseFn and GraphQLValidateFn may return promises even though the built-in parse() and validate() functions remain synchronous. The new standard shape acknowledges that user-supplied parse and validate phases may sometimes be async. You can use a raw GraphQLHarness directly, but we expect and encourage most servers working with this pattern to continue using Envelop or a framework modeled after Envelop.

For observability tooling, v17 publishes lifecycle events through Node.js diagnostics_channel, with channels for parsing, validation, execution, subscription setup, root selection set execution, variable coercion, and field resolution.

Schema correctness and spec alignment

For schema and tooling authors, v17 fixes important default-value modeling bugs. Programmatic defaults are now expected in uncoerced input form, either as raw JavaScript input values with default: { value } or GraphQL literal ASTs with default: { literal }. The older default-value forms still work in v17, but are deprecated. Together with the new valueToLiteral() helper, this means GraphQL.js can make the correct default value available through introspection.

v17 clarifies custom scalar APIs. Some methods are new and some are renamed from the v16 names: coerceOutputValue, coerceInputValue, coerceInputLiteral, and valueToLiteral now describe the actual GraphQL value boundary. The older serialize, parseValue, and parseLiteral names continue to work in v17 as deprecated compatibility aliases.

Directives on directive definitions have graduated from experimental status. Directives can now be applied to directive definitions without a parser flag, directive extensions are part of the normal SDL grammar, and directive deprecation metadata is available through GraphQLDirective, introspection, and schema printing.

Validation coverage is broader: schema validation reports more invalid defaults and duplicate operation roots, and KnownOperationTypesRule validates operations whose root type is missing.

Runtime and tooling polish

The hideSuggestions option is available for hiding “Did you mean …” text from validation errors. Use it alongside NoSchemaIntrospectionCustomRule to reduce schema-shape leakage through both suggestion text and introspection. Resolvers may now return async iterables for list fields, which is especially helpful for streamed lists.

For SDL tooling, printDirective() can now print one directive definition without printing an entire schema. Built-in scalar coercers now accept representable JavaScript bigint values. GraphQLSchema.getField() can resolve meta fields, schema element extensions can use symbol keys, and findSchemaChanges() reports breaking, dangerous, and safe schema changes in one call.

Beyond those stable highlights, v17 also includes experimental specification work.

Experimental specification work

Fragment arguments let a fragment define local variables and let each fragment spread pass values to that fragment. The goal is better colocation: a fragment can declare the inputs required by the selection it owns, and each spread can supply those inputs without forcing every operation that uses the fragment to carry the same variable plumbing. This work was formerly championed by Jovi De Croock, whose spec PR and GraphQL.js implementation moved the proposal forward. The current PR needs a new champion to help carry the work through the specification process.

v17 also includes an experimental preview of more flexible GraphQL error handling. Today’s error propagation can blur meaningful business null values with resolver failures, discard useful partial data, and create normalized-cache issues when a field error bubbles into an ancestor null. Future work is centered on client-selected error modes through onError, including PROPAGATE, NULL, and HALT, plus service discovery for supported modes and defaults.

GraphQL.js v17 does not yet support the full onError request shape or every proposed mode. It supports traditional error propagation, equivalent to PROPAGATE, and one additional experimental mode, equivalent to NULL, via @experimental_disableErrorPropagation. Operations using that directive keep field execution errors in errors with their normal path and set only the errored response position to null. Follow along as we continue this work in v17 through the onError implementation and service capabilities PRs.

Last but certainly not least, v17 includes long-awaited experimental support for incremental delivery with @defer and @stream. GraphQL.js v17 keeps ordinary execute() as a single-result executor and exposes experimentalExecuteIncrementally() for the current incremental response shape. The GraphQL Working Group has revised that response shape extensively, including the new response format with pending, incremental, and completed notices. Hosts that still need the older incremental payload shape from earlier v17 alpha releases can use legacyExecuteIncrementally(), so existing integrations have a migration bridge while new integrations target the current format. This work is part of a multi-year effort, with the tireless spec work led by Rob Richard, including the open spec draft.

These features remain experimental. We are shipping them in GraphQL.js so continued feedback can help refine these proposals.

Thank you

This is the first major release of GraphQL.js since October 2021, with contributors including @IvanGoncharov, @yaacovCR, @thomasheyenbrock, @robrichard, @twof, @spawnia, @lilianammmatos, @glasser, @PabloSzx, @sashashura, @Cito, @igrlk, @benjie, @leebyron, @dylanowen, @tomgasson, @sakesun, @AaronMoat, @colinhacks, @saihaj, @JoviDeCroock, @mjmahone, @andimarek, @n1ru4l, @jasonkuhrt, @hayes, @martinbonnin, @marklarah, @NeoPhi, @ardatan, @hkmu, @xonx4l, @ryym, @jerelmiller, @abishekgiri, @yuchenshi, @jbellenger, @BoD, @Nols1000, @Malien, @logaretm, @sarahxsanders, @fallintoplace, and @Urigo. We are grateful to them as well as everyone who helped move GraphQL.js v17 forward through issues, reviews, testing, design discussion, documentation, migration feedback, and user reports.

We particularly want to thank past maintainers Ivan Goncharov and Jovi De Croock for their contributions to GraphQL, GraphQL.js, and their stewardship of important portions of this release.

As always, a huge thanks to Lee Byron for building such a wonderful GraphQL community, and to that community for sustaining this work.

What is next?

To help shape what comes next, follow and contribute in the graphql/graphql-js repository, especially the future work discussion in graphql/graphql-js#4818. You can also join a GraphQL.js Working Group meeting or reach us in the #graphql-js channel on the GraphQL Discord server. We would love your help!

Happy coding,

Yaacov Rydzinski @yaacovCR