필사 모드: GraphQL Spec, Almost Four Years Later — What Made It Into the September 2025 Edition and GraphQL.js 17 GA, and What Didn't
English- Introduction — A Standard That Seemed Frozen, Moving Again
- Get the Dates Right First — Timeline of the Two Releases
- The September 2025 Edition — What's Worth Taking From Four Years of Changes
- What Didn't Make the Spec — defer and stream Are Still an RFC
- GraphQL.js 17 — What Breaks, What Gets Better
- Where Federation Standardization Stands — Composite Schemas Is at Stage 0
- So What Should You Do Now
- Closing
- References
Introduction — A Standard That Seemed Frozen, Moving Again
For a long time, the GraphQL spec's last edition was October 2021. In the meantime the ecosystem kept moving — Apollo Federation and various gateways and clients each evolved on their own — but the standard document itself sat frozen for almost four years. It was a gap wide enough to invite the cynical question, "Is GraphQL in maintenance mode now?"
That gap was broken by two recent releases. In September 2025, the September 2025 edition shipped — in the words of the official announcement, the first edition since October 2021 — and on June 15, 2026, the reference implementation, GraphQL.js v17, went GA. The first v17 alpha landed on npm on May 19, 2022, which means it took more than four years to shed the alpha label.
This post takes the two releases apart without the hype: what actually made it into the standard, what's still missing (spoiler: @defer and @stream), and what your team should do about it right now. Every date and version here was verified directly against the spec text, the npm registry, and GitHub releases.
Get the Dates Right First — Timeline of the Two Releases
GraphQL Spec (spec.graphql.org)
2015-07 first working draft (July 2015)
2021-10-26 October 2021 edition
2025-09-03 September 2025 edition <- 3 years, 10 months later
2026-06-04 current working draft updated (prepping the next edition)
GraphQL.js (registry.npmjs.org publish timestamps)
2022-05-19 17.0.0-alpha.1
2025-06-11 17.0.0-alpha.9
2026-05-07 17.0.0-beta.0
2026-06-02 17.0.0-rc.0
2026-06-15 17.0.0 GA <- a little over 4 years after alpha began
2026-07-03 17.0.2 (current latest)
16.x is still getting patched: 16.13.0 (2026-02-24), 16.14.2 (2026-06-09)
According to the spec's release notes, the September 2025 edition folds in more than 100 changes from 30 contributors since October 2021, and it also happens to land ten years after the first draft (July 2015).
The September 2025 Edition — What's Worth Taking From Four Years of Changes
The official changelog lists five major changes. Let's take them one at a time.
OneOf Input Objects — Standardizing the "Input Union"
GraphQL has had unions for output types but not for input types, so there was no way to express "look up by ID, or by email, but only one of the two" directly in the schema. Everyone left every field nullable and hand-validated in the resolver. This problem, dragging on for years under RFC #825, has now been standardized as @oneOf.
input UserUniqueCondition @oneOf {
id: ID
username: String
organizationAndEmail: OrganizationAndEmailInput
}
In the spec's own words, exactly one field of a OneOf input object must be provided, and its value must not be null. Fill in two fields and you get a request error; fill in one with null and it's also an error. That validation is now a spec-level input coercion rule, not resolver code. Introspection gains a __Type.isOneOf field so codegen tools can generate the union type correctly.
Schema Coordinates — An Addressing System for Schema Elements
RFC #794 gives every element of a schema a unique string address.
Business type
Business.name field
SearchCriteria.filter input field
SearchFilter.OPEN_NOW enum value
Query.searchBusiness(criteria:) field argument
@private directive
@private(scope:) directive argument
The spec guarantees each element has exactly one coordinate. It looks trivial, but it's a shared vocabulary for the tooling ecosystem — schema diffs, lint rules, error reporting, and usage tracking ("who's using this field") had each used their own notation until now. There's already a follow-up RFC proposing that errors carry schema coordinates.
The Rest: Document Descriptions, Expanded Deprecation, Full Unicode
- Executable documents (queries and mutations) can now carry descriptions too (#1170). The changelog's stated motivation is interesting — it's for AI consumers, so the context an LLM needs to understand a query's intent can live in the document itself.
- Deprecation now extends across schema elements more broadly, with the rules tightened up — a constraint that an implementing type can't deprecate a field its interface hasn't deprecated, the
includeDeprecatedargument going non-null, and more (#1053, #1142). - The language grammar now supports the full Unicode range (#849).
- Default-value coercion rules were redefined (#793), and the execution algorithm's description was substantially cleaned up (
ExecuteSelectionSetreplaced byExecuteCollectedFields, among other changes).
Taken as a whole, this edition isn't a feast of new features — it's four years of consistency debt paid down, plus two substantial language features (OneOf, Coordinates). The official announcement frames it around being AI-ready, but the substance is closer to solid standards maintenance.
What Didn't Make the Spec — defer and stream Are Still an RFC
This is the part of the release that deserves the most honest look. @defer and @stream — incremental delivery, splitting a response and sending it out in pieces — are not in the September 2025 edition. I confirmed this by searching the spec text directly: the word "defer" appears zero times. It's not in the June 2026 working draft either.
To get a sense of how old this story is: the graphql package's npm registry has carried an experimental-stream-defer experimental build since October 2020, and that dist-tag still points to a 16.1.0-series build published in December 2021. The Incremental Delivery RFC currently sits at RFC 2 (Draft) — it's not dead. A commit landed on the spec draft the very week I'm writing this (2026-07-15). But that doesn't change the fact that it's been "almost done" for more than five years.
Why does it take this long? Part of it is the difficulty of standardizing execution semantics (duplicate field handling, error propagation) and the payload format, but part of it is also that real-world data isn't uniform. At GraphQLConf 2025, Meta announced its own directive called @async, citing the hidden costs @defer leaves behind — which means the organization running GraphQL at the largest scale in the world is experimenting with an alternative to defer. The very target of standardization is still shifting under everyone's feet.
The practical implication is clear. Any stack using defer/stream today is running a pre-spec feature and carries the risk that the payload format can change. It already has — that's the v17 story right below.
GraphQL.js 17 — What Breaks, What Gets Better
The official upgrade guide is unusually thorough, so here I'll just pull out the essentials you need to make a call.
What Breaks
- The Node.js requirement rises to
^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0. If your environment is locked to Node 20 or below, this is where it stops. - The package switches to
exports-based conditional exports. Deep imports outside the official entry points (graphql,graphql/execution, etc.) are now explicitly stated to have no semver guarantee, and the already-deprecatedgraphql/subscriptionsubpath has been removed. - Development mode switches from automatic
NODE_ENV-based activation to explicit opt-in (enableDevMode()or thedevelopmentpackage condition). - Custom scalar API names are cleaned up —
serialize,parseValue, andparseLiteralremain as deprecated aliases, whilecoerceOutputValue,coerceInputValue, andcoerceInputLiteralare the new names. subscribe()'s return type changes from a fixed Promise to PromiseOrValue, so TypeScript code that assumed a Promise will need fixing.
Executor Separation — execute Is Single-Result Only
The most conspicuous design decision in v17 is the physical separation of stable and experimental features. execute() is now a stable, single-result-only executor, and it outright rejects a schema that has @defer/@stream directives. To use incremental delivery you have to call a separate function whose name nails down that it's experimental.
import { experimentalExecuteIncrementally } from 'graphql';
const result = await experimentalExecuteIncrementally({ schema, document });
if ('initialResult' in result) {
send(result.initialResult);
for await (const subsequent of result.subsequentResults) {
send(subsequent);
}
} else {
send(result); // for cases that didn't need incremental delivery
}
A change that matters to teams who put defer/stream into production during the alpha years: the payload format changed. The early alpha format identified payloads by path and label, and field data could be duplicated; the current format registers pending entries by id and announces completion with completed. legacyExecuteIncrementally() remains for hosts that need the old format, but as the name says, it's for a transition period. This is a concrete example of the bill that comes due for using a pre-spec feature early.
There's also one experimental feature on the error-propagation side. Attaching @experimental_disableErrorPropagation to an operation makes an execution error on a non-null field null out just that location instead of cascading null up through its ancestors — aimed at the problem where, in clients using normalized caches, a nulled-out ancestor gets mistaken for a real data update. It's a preview implementation of the ongoing error-behavior (onError) spec discussion, and again, "experimental" is baked right into the name.
What's Useful for Operators — AbortSignal, diagnostics_channel, Harness
Beyond what breaks, what you gain is substantial too.
- AbortSignal support arrives as a first-class feature. Request timeouts, client disconnects, and downstream API cancellation can now be unified into a single cancellation path, and resolvers access it via
info.getAbortSignal(). If execution is aborted after partial data has been produced,AbortedGraphQLExecutionErrorexposes both the abort reason and the partial result. - Lifecycle events for parsing, validation, execution, and field resolution are now published via Node.js
diagnostics_channel. APM instrumentation becomes possible without monkey-patching. GraphQLHarnessgives you an official way to swap out the parse/validate/execute/subscribe stages. The announcement itself states that it follows the staged model Envelop proved out — a case of a community pattern being re-imported into the reference implementation.- For framework authors, lower-level boundaries of the execution pipeline (
validateExecutionArgs(),executeRootSelectionSet(), etc.) are now exposed, letting you build a custom executor without copying internal code.
In other words, v17's real substance isn't defer/stream — it's the server operations surface: cancellation, instrumentation, extension points.
Where Federation Standardization Stands — Composite Schemas Is at Stage 0
A status check for anyone waiting on "standardization of federated GraphQL." The Apollo Federation covered in an earlier post is, after all, just one vendor's spec, and since May 2024 the GraphQL Foundation has been building a vendor-neutral standard in the Composite Schemas Working Group.
The honest state of things as of July 2026: the spec repository's README still states Stage 0 (Preliminary) — pre-Draft, and content is subject to change. GitHub releases: zero. That said, it's nowhere near a dead project — several commits refining the @key/@require rules merged as recently as July 9, 2026, and in a May 2026 GraphQLConf keynote, The Guild's Uri Goldshtein assessed that federation is no longer a frontier experiment but a well-paved road the industry now walks with confidence. The practical takeaway: if you're designing a federated architecture right now, you're still picking a vendor spec (the Apollo Federation v2 family), and Composite Schemas is something to track only as "a direction you might switch to someday."
One governance-side change is also worth recording. GAPs (GraphQL Auxiliary Proposals), announced in June 2026, is an official repository that gathers conventions the core spec deliberately doesn't cover — things like pagination connections and @cost — into community specs. Core spec kept narrow, surrounding conventions routed to GAPs, distributed execution routed to Composite Schemas — the picture is standardization splitting into three layers. For what it's worth, around the same time, Meta used its GraphQLConf 2026 keynote ("The Creator's Curse") to reflect that the ten-year-old promise of being "easy to learn and use" hadn't fully held up at a scale of thousands of engineers, and talked about an internal redesign, while Apollo CEO Matt DeBergalis declared that "GraphQL can and must be the language of AI." The standard is in maintenance mode while vendors tell an AI story — there's a temperature gap at this moment.
So What Should You Do Now
Here's a summary as decision criteria.
Clear reasons to move to v17
- You're building a server framework, gateway, or instrumentation tool — the execution boundary APIs, Harness, and
diagnostics_channelare the point in themselves. - Request cancellation is a real problem for your service — there's no clean alternative to AbortSignal integration in v16.
- A new project — v17 is now latest, and the docs and API reference have been reorganized around v17.
Cases where there's no need to rush
- Environments locked to below Node 22. The engine requirement is a hard gate.
- Codebases with a lot of custom code relying on deep imports or internal APIs — enforced
exportsand executor separation mean more spots to fix. - 16.x isn't an abandoned branch — a 16.14.2 patch shipped as recently as June 2026. If you're not missing any feature right now, it's not too late to move once you've confirmed the peerDependencies of your surrounding libraries accept 17.
Be more conservative about defer/stream
- If this is a new adoption: the spec is still at RFC 2, and even in v17 the function name has "experimental" baked in. The fact that the payload format got overhauled once already during the alpha years is the best warning sign there is. Don't turn it on until you've confirmed the gateway, client, and cache all the way through support the same format.
- If you're already running on the alpha format:
legacyExecuteIncrementally()can buy you time, but it's better to set a migration plan toward the new pending/completed format now.
One more thing — from a schema-design standpoint, @oneOf is something you can get for free right away. If you have input types with a "look up by one of several keys" pattern, the refactor of moving resolver validation code into a schema declaration is low-risk, and client type-generation quality improves immediately. Of course, this too requires first confirming that your server and client libraries have implemented the September 2025 edition — there's always a lag between a spec release and ecosystem implementation.
Closing
To sum up: the GraphQL standard broke almost four years of silence and returned with the September 2025 edition, and its contents lean practical — consistency debt paid down plus OneOf plus Schema Coordinates — rather than a showcase of flashy new features. The reference implementation, GraphQL.js 17, came out of four years of alpha as a GA release that drew the boundary between stable execution and experimental features (defer/stream, error-propagation control) at the code level. Meanwhile defer/stream is on its tenth year outside the standard, and the federation standard (Composite Schemas) sits at Stage 0.
You could read this picture cynically — "four years for this?" But reading it the other way round is more useful in practice. This is a project that chooses to wait ten years rather than put a breaking change into the spec, which is exactly why a server built against the October 2021 spec is still valid today. Stability is the default, and experiments come labeled as such — that's a property worth wanting from the foundational technology of your API layer. Compared against how Protobuf tightens its defaults through an Edition system around the same time, you can see both camps converging on "managing migration cost over chasing innovation." Instead of spending time disappointed at the standard's pace, go draft your @oneOf refactor and your v17 upgrade estimate.
References
- GraphQL Specification Versions — spec.graphql.org
- The Full Text of the September 2025 Edition
- Announcing the September 2025 Edition of the GraphQL Specification — graphql.org blog
- September 2025 Official Changelog — graphql/graphql-spec
- Introducing GraphQL.js v17 — graphql.org blog
- GraphQL.js v16→v17 Official Upgrade Guide
- GraphQL.js Defer/Stream Docs
- GraphQL RFC Tracker — Incremental Delivery and other statuses
- graphql/composite-schemas-spec Repository (Stage 0)
- Announcing the Composite Schemas Working Group (2024-05)
- Introducing GAPs: GraphQL Auxiliary Proposals (2026-06)
- GraphQLConf 2026 wrap-up (2026-07-14)
- What Launched at GraphQLConf 2025 — Meta's @async mention
- graphql package npm registry (source of versions/dates)
현재 단락 (1/99)
For a long time, the GraphQL spec's last edition was [October 2021](https://spec.graphql.org/). In t...