---
title: Upgrading from v16 to v17
sidebarTitle: v16 to v17
---

import { Tabs } from 'nextra/components';
import { Callout } from 'nextra/components'
 
<Callout type="info" emoji="ℹ️">
  Currently GraphQL v17 is in alpha, this guide is based on the alpha release and is subject to change.
</Callout>

# Breaking changes

## Required Node.js versions

The v17 release drops support for end-of-life versions of Node.JS, retaining support for versions 20, 22, and 24 or above.

## ESM and conditional exports

Earlier versions of GraphQL.js shipped dual builds for CommonJS and ESM, with ESM ".mjs" files sitting alongside CommonJS ".js" files.
The ESM build was accessible via tooling recognizing the `module` field in `package.json`, while the CommonJS build was accessible via
the `main` field. Unless configured carefully, this could sometimes lead to multiple copies of GraphQL.js being loaded in the same
process, i.e. the dual-package hazard.

v17 enables access to the ESM build via the `exports` field in `package.json` in a scheme designed to avoid the dual-package hazard as
best as possible, relying on the ability of bun and newer versions of Node.js to consistently load ESM modules via `require` by
indicating support for specific conditions. __ESM will now be served by default__, unless the requesting environment tooling __both__
(A) supports the `node` or `require` conditions __and__ (B) does NOT support `bun`, `module`, `module-sync`. In that scenario, the
CommonJS build will be served instead.

Note: ESM is not served to deno even though it supports require(esm) because deno not yet support the `module-sync` condition, nor
does it seem to provide the `deno` condition when calling `require`, see https://github.com/denoland/deno/issues/29970.

Deno users can access a Typescript build for deno via git://github.com/graphql/graphql-js.git#deno as well as by specifically loading
the index.mjs file, i.e. `import { graphql } from 'graphql/index.mjs'`, although this does not protect against the dual-package hazard.

## Development mode no longer enabled by default and no longer dependent on NODE_ENV value

GraphQl.js development mode in v17 is disabled by default and can be enabled by the `development` condition on supporting platforms or
by explicitly enabling it within user code by calling `enableDevMode()`. Development mode may trigger permanent de-optimizations and
therefore cannot be disabled once enabled. The new `isDevModeEnabled()` function can be used to check whether development mode has
been enabled.

GraphQL.js development mode no longer depends on the `NODE_ENV` environment variable; build tools other than Node.js no longer need
to replace this Node.js specific code. See [Development Mode](./development-mode) for further details regarding how to enable these
checks in v17.

## Default values

GraphQL schemas allow default values for input fields and arguments. Historically, GraphQL.js did not rigorously validate or coerce these
defaults during schema construction, leading to potential runtime errors or inconsistencies. For example:

- A default value of "5" (string) for an Int-type argument would pass schema validation but fail at runtime.
- Internal serialization methods like astFromValue could produce invalid ASTs if inputs were not properly coerced.

With the new changes default values will be validated and coerced during schema construction.

```graphql
input ExampleInput {
  value: Int = "invalid"  # Now triggers a validation error
}
```

This goes hand-in-hand with the deprecation of `astFromValue` in favor of `valueToLiteral` or `default: { value: <externalValue> }`.

```ts
// Before (deprecated)
const defaultValue = astFromValue(internalValue, type);
// After
const defaultValue = valueToLiteral(externalValue, type);
```

If you want to continue using the old behavior, you can use `defaultValue` in your schema definitions. The new
behavior will be exposed as `default: { literal: <literal> }`.

## GraphQLError constructor arguments

The `GraphQLError` constructor now only accepts a message and options object as arguments. Previously, it also accepted positional arguments.

```diff
- new GraphQLError('message', 'source', 'positions', 'path', 'originalError', 'extensions');
+ new GraphQLError('message', { source, positions, path, originalError, extensions });
```

## `createSourceEventStream` arguments

The `createSourceEventStream` function now only accepts an object as an argument. Previously, it also accepted positional arguments.

```diff
- createSourceEventStream(schema, document, rootValue, contextValue, variableValues, operationName);
+ createSourceEventStream({ schema, document, rootValue, contextValue, variableValues, operationName });
```

## `execute` will error for incremental delivery

The `execute` function will now throw an error if it sees a `@defer` or `@stream` directive. Use `experimentalExecuteIncrementally` instead.
If you know you are dealing with incremental delivery requests, you can replace the import.

```diff
- import { execute } from 'graphql';
+ import { experimentalExecuteIncrementally as execute } from 'graphql';
```

## Remove incremental delivery support from `subscribe`

In case you have fragments that you use with `defer/stream` that end up in a subscription,
use the `if` argument of the directive to disable it in your subscription operation

## `subscribe` return type

The `subscribe` function can now also return a non-Promise value, previously this was only a Promise.
This shouldn't change a lot as `await value` will still work as expected. This could lead to
some typing inconsistencies though.

## Remove `singleResult` from incremental results

You can remove branches that check for `singleResult` in your code, as it is no longer used.

## Node support

Dropped support for Node 14 (subject to change)

## Removed `TokenKindEnum`, `KindEnum` and `DirectiveLocationEnum` types

We have removed the `TokenKindEnum`, `KindEnum` and `DirectiveLocationEnum` types,
use `Kind`, `TokenKind` and `DirectiveLocation` instead. https://github.com/graphql/graphql-js/pull/3579

## Removed `graphql/subscription` module

use `graphql/execution` instead for subscriptions, all execution related exports have been
unified there.

## Removed `GraphQLInterfaceTypeNormalizedConfig` export

Use `ReturnType<GraphQLInterfaceType['toConfig']>` if you really need this

## Empty AST collections will be undefined

Empty AST collections will be presented by `undefined` rather than an empty array.

## `Info.variableValues`

The shape of `Info.variableValues` has changed to be an object containing
`sources` and `coerced` as keys.

A Source contains the `signature` and provided `value` pre-coercion for the
variable. A `signature` is an object containing the `name`, `input-type` and
`defaultValue` for the variable.

## Stream directive can't be on multiple instances of the same field

The `@stream` directive can't be on multiple instances of the same field,
this won't pass `validate` anymore.

See https://github.com/graphql/graphql-js/pull/4342

## Stream initialCount becomes non-nullable

The `initialCount` argument of the `@stream` directive is now non-nullable.

See https://github.com/graphql/graphql-js/pull/4322

## GraphQLSchemas converted to configuration may no longer be assumed valid

The `assumeValid` config property exported by the `GraphQLSchema.toConfig()` method now passes through the original
flag passed on creation of the `GraphQLSchema`.
Previously, the `assumeValid` property would be to `true` if validation had been run, potentially concealing the original intent.

See https://github.com/graphql/graphql-js/pull/4244 and https://github.com/graphql/graphql-js/issues/3448

## `coerceInputValue` returns `undefined` on error

`coerceInputValue` now aborts early when an error occurs, to optimize execution speed on the happy path.
Use the `validateInputValue` helper to retrieve the actual errors.

## Removals

- Removed deprecated `getOperationType` function, use `getRootType` on the `GraphQLSchema` instance instead
- Removed deprecated `getVisitFn` function, use `getEnterLeaveForKind` instead
- Removed deprecated `printError` and `formatError` utilities, you can use `toString` or `toJSON` on the error as a replacement
- Removed deprecated `assertValidName` and `isValidNameError` utilities, use `assertName` instead
- Removed deprecated `assertValidExecutionArguments` function, use `assertValidSchema` instead
- Removed deprecated `getFieldDefFn` from `TypeInfo`
- Removed deprecated `TypeInfo` from `validate` https://github.com/graphql/graphql-js/pull/4187

## Deprecations

- Deprecated  `astFromValue` use `valueToLiteral` instead, when leveraging `valueToLiteral` ensure
  that you are working with externally provided values i.e. the SDL provided defaultValue to a variable.
- Deprecated `valueFromAST` use `coerceInputLiteral` instead
- Deprecated `findBreakingChanges()` and `findDangerousChanges()`. Use `findSchemaChanges()` instead, which can also be used to find safe changes.
- Deprecated `serialize`. `parseValue`, and `parseLiteral` properties on scalar type configuration. Use `coerceOutputValue`, `coerceInputValue`, and `coerceInputLiteral` instead.

## Experimental Features

### Experimental Support for Incremental Delivery

- [Spec PR](https://github.com/graphql/graphql-spec/pull/1110) / [RFC](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md)
- enabled only when using `experimentalExecuteIncrementally()`, use of a schema or operation with `@defer`/`@stream` directives within `execute()` will now throw.
- enable early execution with the new `enableEarlyExecution` configuration option for `experimentalExecuteIncrementally()`.

### Experimental Support for Fragment Arguments

- [Spec PR](https://github.com/graphql/graphql-spec/pull/1081)
- enable with the new `experimentalFragmentArguments` configuration option for `parse()`.
- new experimental `Kind.FRAGMENT_ARGUMENT` for visiting
- new experimental `TypeInfo` methods and options for handling fragment arguments.
- coerce AST via new function `coerceInputLiteral()` with experimental fragment variables argument (as opposed to deprecated `valueFromAST()` function).

## Features

- Added `hideSuggestions` option to `execute`/`validate`/`subscribe`/... to hide schema-suggestions in error messages
- Added `abortSignal` option to `graphql()`, `execute()`, and `subscribe()` allows cancellation of these methods;
  `info.abortSignal` can also be used in field resolvers to cancel asynchronous work that they initiate.
- `extensions` support `symbol` keys, in addition to the normal string keys.
- Added ability for resolver functions to return async iterables.
- Added `perEventExecutor` execution option to allows specifying a custom executor for subscription source stream events, which can be useful for preparing a per event execution context argument.
- Added `validateInputValue` and `validateInputLiteral` helpers to validate input values and literals, respectively.
- Added `replaceVariableValues` helper to replace variables within complex scalars uses as inputs. Internally, this allows variables embedded within complex scalars to finally use the correct default values.
- Added new `printDirective` helper.