---
title: What changed in GraphQL.js v15
sidebarTitle: v14 to v15
---
import { Callout } from 'nextra/components';
{/* cspell:ignore buildschema */}
# What Changed in GraphQL.js v15
<Callout type="info">
GraphQL.js v15 is an older release line. If you are upgrading from v14, use
this guide first, then continue through [v15 to v16](/upgrade-guides/v15-v16)
and [v16 to v17](/upgrade-guides/v16-v17).
</Callout>
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
<nav className="upgrade-guide-toc" aria-labelledby="contents">
<div className="upgrade-guide-toc-grid">
<div className="upgrade-guide-toc-group">
<a className="upgrade-guide-toc-title" href="#using-this-guide">
Using this Guide
</a>
</div>
<div className="upgrade-guide-toc-group">
<a className="upgrade-guide-toc-title" href="#platform-and-package-shape">
Platform and Package Shape
</a>
<div className="upgrade-guide-toc-links">
<a href="#nodejs-and-package-dependencies">Node.js</a>
<a href="#typescript-definitions">TypeScript</a>
</div>
</div>
<div className="upgrade-guide-toc-group">
<a
className="upgrade-guide-toc-title"
href="#removed-utilities-and-renamed-entry-points"
>
Removed Utilities
</a>
<div className="upgrade-guide-toc-links">
<a href="#input-coercion-helpers">Input coercion</a>
<a href="#introspection-query-helper">Introspection</a>
<a href="#lexer-construction">Lexer</a>
<a href="#file-level-imports">File imports</a>
</div>
</div>
<div className="upgrade-guide-toc-group">
<a
className="upgrade-guide-toc-title"
href="#schema-construction-and-sdl"
>
Schema and SDL
</a>
<div className="upgrade-guide-toc-links">
<a href="#schema-extensions-in-buildschema">Extensions</a>
<a href="#interfaces-implementing-interfaces">Interfaces</a>
<a href="#schema-descriptions">Descriptions</a>
<a href="#custom-scalar-specification-urls">Scalar URLs</a>
<a href="#deprecating-input-values">Deprecated inputs</a>
<a href="#empty-deprecation-reasons">Empty reasons</a>
</div>
</div>
<div className="upgrade-guide-toc-group">
<a className="upgrade-guide-toc-title" href="#coercion-and-serialization">
Coercion and Serialization
</a>
<div className="upgrade-guide-toc-links">
<a href="#enum-values">Enum values</a>
<a href="#built-in-scalars">Built-in scalars</a>
</div>
</div>
<div className="upgrade-guide-toc-group">
<a className="upgrade-guide-toc-title" href="#execution-and-validation">
Execution and Validation
</a>
<div className="upgrade-guide-toc-links">
<a href="#request-api-object-arguments">Request APIs</a>
<a href="#synchronous-execution-helper">Sync execution</a>
<a href="#execution-result-and-error-formatting">Result formatting</a>
<a href="#deprecated-usage-validation">Deprecated usage</a>
<a href="#recommended-validation-rules">Recommended rules</a>
</div>
</div>
<div className="upgrade-guide-toc-group">
<a className="upgrade-guide-toc-title" href="#practical-migration-order">
Migration Work
</a>
<div className="upgrade-guide-toc-links">
<a href="#v15-compatibility-apis-to-clear-before-v16">v16 prep</a>
<a href="#practical-migration-order">Order</a>
<a href="#detailed-migration-checklists">Checklists</a>
<a href="#deep-import-moves">Deep imports</a>
</div>
</div>
</div>
</nav>
## 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:
```json
{
"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.
```diff
- 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.
```diff
- 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.
```diff
- 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](#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.
```graphql
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.
```graphql
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.
```graphql
"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.
```js
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.
```graphql
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.
```diff
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.
```js
const Episode = new GraphQLEnumType({
name: 'Episode',
values: { NEW_HOPE: { value: 4 } },
});
Episode.serialize(5); // throws in v15
```
### Built-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.
```js
const value = { valueOf: () => 7 };
GraphQLInt.serialize(value); // 7 in v15
```
If 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.
```diff
- 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.
```js
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.
```js
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.
```js
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()`, and `subscribe()` calls to object arguments.
- Replace `findDeprecatedUsages()` with `validate()` plus
`NoDeprecatedCustomRule`.
- Replace comment descriptions and `commentDescriptions` usage with string or
block-string descriptions. Read `description` fields directly instead of
calling `getDescription()`.
- Remove `allowLegacySDLEmptyFields` and
`allowLegacySDLImplementsInterfaces`; use valid SDL instead.
- Replace `GraphQLSchema.isPossibleType()` with `schema.isSubType()`.
- Replace `field.isDeprecated` and `enumValue.isDeprecated` reads with
`deprecationReason != null`.
- Plan to rename scalar specification URL config and introspection reads from
`specifiedByUrl` to `specifiedByURL` when moving to v16.
## Practical Migration Order
Use this order to keep the upgrade reviewable. The checklists below expand
each category into concrete items.
1. Update Node.js and remove runtime assumptions provided by old package
dependencies.
2. Replace removed utilities and lexer construction helpers.
3. Update schema construction and SDL features, then run `validateSchema()`.
4. Update enum, scalar, and input coercion expectations.
5. Clear v15 compatibility APIs that v16 removes.
6. 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](#deep-import-moves) for moved or renamed file paths.
### Removed APIs and required replacements
- Replace `coerceValue()` with `coerceInputValue()`.
- Replace `isValidJSValue()` and `isValidLiteralValue()` with input coercion or
document validation, depending on the source of the value.
- Replace `introspectionQuery` with `getIntrospectionQuery()`.
- Replace `createLexer(source, options)` with `new Lexer(source)`.
- Remove `allowedLegacyNames` usage from `GraphQLSchema` construction.
### 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.description` if your tooling serializes schema
metadata.
- Use `specifiedByUrl` or `@specifiedBy` for 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()`, and
`subscribe()` positional calls to object arguments.
- Replace `findDeprecatedUsages()` with `validate()` plus
`NoDeprecatedCustomRule`.
- Replace comment descriptions and `commentDescriptions` usage with string or
block-string descriptions.
- Remove `allowLegacySDLEmptyFields` and
`allowLegacySDLImplementsInterfaces` usage.
- Replace `GraphQLSchema.isPossibleType()` with `schema.isSubType()`.
- Replace `field.isDeprecated` and `enumValue.isDeprecated` reads with
`deprecationReason != null`.
- Rename scalar specification URL config and introspection reads from
`specifiedByUrl` to `specifiedByURL` when moving to v16.
### Coercion and execution
- Remove `value: undefined` from enum value configs.
- Update tests that expected enum `serialize()`, `parseValue()`, or
`parseLiteral()` to return `undefined` for invalid values.
- Update Float execution or `astFromValue()` tests that expected `NaN` to
become `null` without 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.extensions` in TypeScript helpers that wrap
execution results.
### Optional v15 features
- Use `GraphQLError.toJSON()` and `GraphQLFormattedError` types for formatted
error output.
- Use `NoDeprecatedCustomRule` when deprecated schema elements should fail
validation.
- Use `NoSchemaIntrospectionCustomRule` only for hosts that intentionally block
introspection.
- Consider `recommendedRules` and `MaxIntrospectionDepthRule` for public
servers.
- Request `specifiedByUrl` in 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/schemaPrinter` moved to
`graphql/utilities/printSchema`. The public function names did not change:
`printSchema()`, `printType()`, and `printIntrospectionSchema()` keep their
names.
- The `getIntrospectionQuery()` file-level import moved from
`graphql/utilities/introspectionQuery` to
`graphql/utilities/getIntrospectionQuery`. The function name did not change;
the old `introspectionQuery` string constant did not move and should be
replaced as described above.
- Validation rule files were renamed to match the public `*Rule` export
convention. For example,
`graphql/validation/rules/FieldsOnCorrectType` became
`graphql/validation/rules/FieldsOnCorrectTypeRule`, and the exported rule is
`FieldsOnCorrectTypeRule`. The same convention applies to the v14 specified
validation rules such as `ValuesOfCorrectType`, `NoUnusedVariables`, and
`ProvidedRequiredArguments`.
- The type-only helper `graphql/tsutils/Maybe` moved to
`graphql/jsutils/Maybe`, with a named `Maybe` type export instead of the old
default type export.