What Changed in GraphQL.js v15
GraphQL.js v15 is an older release line. GraphQL.js v16 is the current stable release line, and a v17 release candidate is available for final testing and feedback. If you are upgrading from v14, use this guide first, then continue through v15 to v16 and v16 to v17.
GraphQL.js v15 is mainly a compatibility cleanup and SDL modernization release.
It keeps the core v14 request flow: parse, validate, execute, and use the
schema types from graphql/type. Most migration work is around removed
deprecated utilities, newer schema-language features, and stricter enum and
scalar coercion behavior. Complete this step before applying later
major-version guides so failures stay tied to one version boundary.
Contents
Using this Guide
This guide is for projects that depend on GraphQL.js, including applications, servers, libraries, and tools. It compares the latest available v14 and v15 release lines, and focuses on differences that still exist between those lines. If a change first appeared during the v15 line but also shipped in the latest v14 line, it is not listed here as v14-to-v15 migration work.
Changes described below use these labels:
- Breaking change: v14 code may need to change before it runs on v15.
- Behavioral tightening: v15 validates, coerces, or reports a case more precisely.
- Deprecation: the v14 API still works in v15, but should be migrated before v16.
- New stable API: a new public API that can be adopted independently.
- Experimental or opt-in: available in v15, but proposal-backed or outside the default request path.
Platform and Package Shape
Node.js and package dependencies
Breaking change. GraphQL.js v15 requires Node.js 10 and later:
{
"engines": {
"node": ">= 10.x"
}
}Upgrade Node.js before upgrading GraphQL.js. This separates runtime and package-manager errors from GraphQL.js migration errors.
Breaking change. GraphQL.js v15 no longer depends on its old iterator
helper package or babel-polyfill. Subscription and async-iterator code uses
native iterator protocols. If a browser or non-Node runtime does not provide
the iterator features your application needs, provide those polyfills in the
host application.
TypeScript definitions
Breaking change. The published TypeScript definitions were reorganized, but
public imports from graphql, graphql/language, graphql/type,
graphql/execution, graphql/subscription, graphql/utilities, and
graphql/validation continue to work.
GraphQL.js v15 also removes several TypeScript-only generic parameters that did not exist in the JavaScript runtime surface, including the extra data, source, and args type parameters on common execution and field types. Let those types infer from the remaining public generics instead of passing removed parameters.
Removed Utilities and Renamed Entry Points
Input coercion helpers
Breaking change. The deprecated coerceValue() helper was removed. Use
coerceInputValue() for JavaScript input values.
- import { coerceValue } from 'graphql/utilities';
+ import { coerceInputValue } from 'graphql/utilities';
- const result = coerceValue(value, inputType);
+ const result = coerceInputValue(value, inputType);coerceInputValue() reports errors through an optional callback. If the
callback is omitted, it throws on the first invalid value.
Breaking change. isValidJSValue() and isValidLiteralValue() were
removed. Use coerceInputValue() when you need to validate a JavaScript input
value, and use validate() plus the specified validation rules when you need
to validate a document.
Introspection query helper
Breaking change. The deprecated introspectionQuery string constant was
removed. Use getIntrospectionQuery() so callers can request optional
introspection fields supported by the target server.
- import { introspectionQuery } from 'graphql';
+ import { getIntrospectionQuery } from 'graphql';
- const source = introspectionQuery;
+ const source = getIntrospectionQuery();Lexer construction
Breaking change. createLexer() was removed. Use the Lexer class with a
Source instance.
- import { createLexer, Source } from 'graphql/language';
+ import { Lexer, Source } from 'graphql/language';
const source = new Source('{ field }');
- const lexer = createLexer(source);
+ const lexer = new Lexer(source);Lexer, Location, and Token are public classes in v15. Code that only uses
parse() does not need to construct them directly.
File-level imports
Breaking change for file-level imports. GraphQL.js only treats the root
package and package-module entry points as semver-stable API boundaries.
Prefer imports from graphql, graphql/language, graphql/type,
graphql/execution, graphql/subscription, graphql/utilities, and
graphql/validation.
If you import individual files, v15 changes a few file names and export names. The most common cases are summarized in Deep Import Moves.
Schema Construction and SDL
Schema extensions in buildSchema()
New stable API. buildSchema() accepts documents that contain type,
interface, union, enum, input object, scalar, directive, and schema extensions.
In v14, buildSchema() ignored extension definitions and only the original
definition contributed fields.
type Query {
a: String
}
extend type Query {
b: String
}In v15, the resulting Query type has both a and b. If you had custom SDL
preprocessing that separated definitions from extensions before calling
buildSchema(), simplify it and let GraphQL.js apply the extensions.
Interfaces implementing interfaces
New stable API. SDL and code-first schemas can model interfaces that implement other interfaces.
interface Node {
id: ID
}
interface Resource implements Node {
id: ID
}Run validateSchema() after migration. Object types implementing an interface
inherit the same field subtype obligations that apply to directly implemented
interfaces.
Schema descriptions
New stable API. Schema definitions can have descriptions.
"Public API schema"
schema {
query: Query
}GraphQLSchema also exposes description. Code that serializes schema
metadata should decide whether to preserve or display the new description
field.
Custom scalar specification URLs
New stable API. v15 supports the @specifiedBy directive and the
programmatic specifiedByUrl scalar configuration field.
const URLScalar = new GraphQLScalarType({
name: 'URL',
specifiedByUrl: 'https://example.com/url-spec',
});printSchema() emits @specifiedBy(url: ...), buildSchema() reads it, and
introspection can include specifiedByUrl when requested with
getIntrospectionQuery({ specifiedByUrl: true }).
Deprecating input values
New stable API. v15 supports @deprecated on field arguments, directive
arguments, and input object fields.
input SearchInput {
oldField: String @deprecated(reason: "Use newField")
newField: String
}
type Query {
search(oldArg: String @deprecated(reason: "Use input")): String
}Introspection fields such as args and inputFields accept
includeDeprecated. Clients and tooling that assume those lists contain only
currently recommended values should keep the default
includeDeprecated: false.
Empty deprecation reasons
Behavioral tightening. Empty deprecation reasons count as deprecations. In
v14, a field with deprecationReason: '' could appear as not deprecated. In
v15, the field is deprecated and the empty reason is preserved.
If code checks field.isDeprecated, prefer checking
field.deprecationReason != null so the same logic works with the v16 field
shape too.
Coercion and Serialization
Enum values
Breaking change. GraphQLEnumType no longer preserves undefined as a
custom internal enum value. In v14, { value: undefined } made
parseValue() return undefined for that enum name and let
serialize(undefined) return the enum name. In v15, value: undefined is
treated like an omitted value, so the internal value becomes the enum name
and serialize(undefined) is invalid.
const Episode = new GraphQLEnumType({
name: 'Episode',
values: {
- NEW_HOPE: { value: undefined },
+ NEW_HOPE: {},
},
});Omit value when the runtime value should be the enum name. If you used
undefined as a real sentinel value, replace it with an explicit runtime value
before upgrading.
Behavioral tightening. GraphQLEnumType.serialize(),
GraphQLEnumType.parseValue(), and GraphQLEnumType.parseLiteral() throw
when the provided value does not match a known enum value. v14 returned
undefined for several invalid values.
const Episode = new GraphQLEnumType({
name: 'Episode',
values: { NEW_HOPE: { value: 4 } },
});
Episode.serialize(5); // throws in v15Built-in scalars
Behavioral tightening. Float execution now reports NaN serialization
as a field error. GraphQLFloat.serialize(NaN) already threw in the latest v14
line, but returning NaN from a resolver for a Float field could still
produce a null JSON value without a field error. In v15, the field reports the
serialization error.
astFromValue(NaN, GraphQLFloat) also throws in v15.
New stable behavior. GraphQLInt, GraphQLFloat, and GraphQLBoolean
serialize object wrappers through their primitive conversion hooks.
const value = { valueOf: () => 7 };
GraphQLInt.serialize(value); // 7 in v15If your resolvers accidentally return boxed or object-wrapped scalar values, v15 may now accept them where v14 reported errors. Prefer returning primitive JavaScript values from resolvers for predictable behavior.
Execution and Validation
Request API object arguments
New stable API. v15 accepts object arguments for graphql(),
graphqlSync(), execute(), executeSync(), and subscribe(). The v14
positional forms still work in v15, but the object forms are the
v16-compatible request API shape.
- const result = await graphql(schema, source, rootValue, contextValue);
+ const result = await graphql({
+ schema,
+ source,
+ rootValue,
+ contextValue,
+ });If you plan to continue to v16, convert these call sites while still on v15 so the mechanical API change is separate from the major-version bump.
Synchronous execution helper
New stable API. executeSync() is available from the root package and
graphql/execution. Use it when every resolver is expected to complete
synchronously and an asynchronous resolver should be treated as a programming
error.
import { executeSync, parse } from 'graphql';
const result = executeSync({ schema, document: parse('{ viewer { id } }') });Execution result and error formatting
New stable API. The TypeScript ExecutionResult type includes optional
extensions. This matches GraphQL responses that carry out-of-band metadata
such as tracing identifiers or cache hints.
New stable API. GraphQLError has toJSON(), and the package exports
GraphQLFormattedError types for formatter code. Code that stringifies errors
through JSON can rely on toJSON() rather than duplicating GraphQL error
formatting logic.
Deprecated-usage validation
New stable API. NoDeprecatedCustomRule is available as a validation rule
for operations that should reject deprecated fields, arguments, input fields,
or enum values.
import { NoDeprecatedCustomRule, validate } from 'graphql';
const errors = validate(schema, document, [NoDeprecatedCustomRule]);findDeprecatedUsages() still exists in v15, but a validation rule is easier
to compose with the rest of the request pipeline.
New stable API. NoSchemaIntrospectionCustomRule rejects introspection
fields in executable documents. Use it only for hosts that intentionally block
introspection.
Recommended validation rules
New stable API. The v15 line includes recommendedRules and
MaxIntrospectionDepthRule. specifiedRules remains the GraphQL
specification validation set. recommendedRules contains GraphQL.js-specific
rules that many public servers should consider but that are not required by
the specification.
import { recommendedRules, specifiedRules, validate } from 'graphql';
const errors = validate(schema, document, [
...specifiedRules,
...recommendedRules,
]);v15 Compatibility APIs to Clear Before v16
These APIs still work in v15, but v16 removes them or replaces them with different names or call shapes. Migrate them during the v15 step when possible:
- Convert positional
graphql(),graphqlSync(),execute(),executeSync(), andsubscribe()calls to object arguments. - Replace
findDeprecatedUsages()withvalidate()plusNoDeprecatedCustomRule. - Replace comment descriptions and
commentDescriptionsusage with string or block-string descriptions. Readdescriptionfields directly instead of callinggetDescription(). - Remove
allowLegacySDLEmptyFieldsandallowLegacySDLImplementsInterfaces; use valid SDL instead. - Replace
GraphQLSchema.isPossibleType()withschema.isSubType(). - Replace
field.isDeprecatedandenumValue.isDeprecatedreads withdeprecationReason != null. - Plan to rename scalar specification URL config and introspection reads from
specifiedByUrltospecifiedByURLwhen moving to v16.
Practical Migration Order
Use this order to keep the upgrade reviewable. The checklists below expand each category into concrete items.
- Update Node.js and remove runtime assumptions provided by old package dependencies.
- Replace removed utilities and lexer construction helpers.
- Update schema construction and SDL features, then run
validateSchema(). - Update enum, scalar, and input coercion expectations.
- Clear v15 compatibility APIs that v16 removes.
- Adopt new validation rules and optional v15 schema features where they match your server design.
Run schema validation, operation validation, execution tests, and TypeScript checks after each group. Mechanical changes are easiest to review when they are separated from behavior changes.
Detailed Migration Checklists
Platform and package shape
- Run v15 on Node.js 10 or newer.
- Provide host-level iterator or browser polyfills if you support runtimes that do not have the native iterator features your application uses.
- Update TypeScript code that references removed v14-only generic parameters.
- Prefer public package entry points instead of files under the old top-level declaration tree.
- If you import from individual files, review Deep Import Moves for moved or renamed file paths.
Removed APIs and required replacements
- Replace
coerceValue()withcoerceInputValue(). - Replace
isValidJSValue()andisValidLiteralValue()with input coercion or document validation, depending on the source of the value. - Replace
introspectionQuerywithgetIntrospectionQuery(). - Replace
createLexer(source, options)withnew Lexer(source). - Remove
allowedLegacyNamesusage fromGraphQLSchemaconstruction.
Schema and SDL
- Let
buildSchema()consume extension definitions directly, or remove custom preprocessing that used to compensate for ignored extensions. - Validate schemas that use interface inheritance and make sure implementing object types satisfy inherited fields.
- Preserve
GraphQLSchema.descriptionif your tooling serializes schema metadata. - Use
specifiedByUrlor@specifiedByfor custom scalar specification URLs. - Decide how clients and tooling should handle deprecated arguments and input fields.
- Treat
deprecationReason: ''as a real deprecation.
v16 prep
- Convert
graphql(),graphqlSync(),execute(),executeSync(), andsubscribe()positional calls to object arguments. - Replace
findDeprecatedUsages()withvalidate()plusNoDeprecatedCustomRule. - Replace comment descriptions and
commentDescriptionsusage with string or block-string descriptions. - Remove
allowLegacySDLEmptyFieldsandallowLegacySDLImplementsInterfacesusage. - Replace
GraphQLSchema.isPossibleType()withschema.isSubType(). - Replace
field.isDeprecatedandenumValue.isDeprecatedreads withdeprecationReason != null. - Rename scalar specification URL config and introspection reads from
specifiedByUrltospecifiedByURLwhen moving to v16.
Coercion and execution
- Remove
value: undefinedfrom enum value configs. - Update tests that expected enum
serialize(),parseValue(), orparseLiteral()to returnundefinedfor invalid values. - Update Float execution or
astFromValue()tests that expectedNaNto becomenullwithout an error. - Update scalar wrapper tests if you depended on v14 rejecting boxed or object-wrapped primitive values.
- Use
executeSync()for intentionally synchronous execution paths. - Preserve
ExecutionResult.extensionsin TypeScript helpers that wrap execution results.
Optional v15 features
- Use
GraphQLError.toJSON()andGraphQLFormattedErrortypes for formatted error output. - Use
NoDeprecatedCustomRulewhen deprecated schema elements should fail validation. - Use
NoSchemaIntrospectionCustomRuleonly for hosts that intentionally block introspection. - Consider
recommendedRulesandMaxIntrospectionDepthRulefor public servers. - Request
specifiedByUrlin introspection only when the target server supports it.
Deep Import Moves
These notes apply only to file-level imports. Prefer package-module imports where possible because file-level paths are not semver-stable API boundaries.
graphql/utilities/schemaPrintermoved tographql/utilities/printSchema. The public function names did not change:printSchema(),printType(), andprintIntrospectionSchema()keep their names.- The
getIntrospectionQuery()file-level import moved fromgraphql/utilities/introspectionQuerytographql/utilities/getIntrospectionQuery. The function name did not change; the oldintrospectionQuerystring constant did not move and should be replaced as described above. - Validation rule files were renamed to match the public
*Ruleexport convention. For example,graphql/validation/rules/FieldsOnCorrectTypebecamegraphql/validation/rules/FieldsOnCorrectTypeRule, and the exported rule isFieldsOnCorrectTypeRule. The same convention applies to the v14 specified validation rules such asValuesOfCorrectType,NoUnusedVariables, andProvidedRequiredArguments. - The type-only helper
graphql/tsutils/Maybemoved tographql/jsutils/Maybe, with a namedMaybetype export instead of the old default type export.