Launch GraphOS Studio
Apollo Server 3 is officially deprecated, with end-of-life scheduled for 22 October 2024. Learn more about upgrading to a supported Apollo Server version.

Migrating to Apollo Server 3

⚠️ As of 15 November 2022, Apollo Server 3 is officially deprecated, with end-of-life scheduled for 22 October 2024.

Learn more about deprecation and end-of-life.

3 is deprecated, and we strongly recommend upgrading to Apollo Server 4. If you are currently using Apollo Server 2, we recommend first upgrading to Apollo Server 3 using this guide. Afterward, you can continue upgrading from Apollo Server 3 to 4.

The focus of this major-version release is to provide a lighter, nimbler core library as a foundation for future features and improved extensibility.

Many Apollo Server 2 users don't need to make any code changes to upgrade to Apollo Server 3! This is especially likely if you use the "batteries-included" apollo-server library (as opposed to a middleware-specific library).

This explains which features do require code changes and how to make them. If you encounter issues while migrating, please create an issue.

For a list of all breaking changes, see the changelog.

Bumped dependencies


Apollo Server 3 supports Node.js 12 and later. (Apollo Server 2 supports back to Node.js 6.) This includes all LTS and Current versions at the time of release.

If you're using an older version of Node.js, upgrade your runtime before upgrading to Apollo Server 3.


Apollo Server has a peer dependency on graphql (the core JS implementation), which means you are responsible for choosing the version installed in your app.

Apollo Server 3 supports graphql v15.3.0 and later. (Apollo Server 2 supported graphql v0.12 through v15.)

If you're using an older version of graphql, upgrade it to a supported version before upgrading to Apollo Server 3.

Removed integrations

Apollo Server 2 provides built-in support for and file uploads via the subscriptions-transport-ws and graphql-upload packages, respectively. It also serves GraphQL Playground from its base URL by default.

Apollo Server 3 removes these built-in integrations, in favor of enabling users to provide their own mechanisms for these features.

You can reenable all of these integrations as they exist in Apollo Server 2.


Apollo Server 2 provides limited, built-in support for WebSocket-based GraphQL subscriptions via the subscriptions-transport-ws package. This integration doesn't work with Apollo Server's plugin system or Apollo Studio usage reporting.

Apollo Server 3 no longer contains this built-in integration. However, you can still use subscriptions-transport-ws for subscriptions if you depend on this implementation. Note that as with Apollo Server 2, this integration won't work with the plugin system or Studio usage reporting.

Additionally, Apollo Server 3 no longer re-exports all of the exports from the graphql-subscriptions package such as PubSub.

We hope to add more fully-integrated support to Apollo Server in a future version.

Note that the subscriptions-transport-ws library is no longer maintained. A newer, actively-maintained graphql-ws package exists. These libraries implement different protocols for GraphQL subscriptions over WebSocket, so you need to adjust your client to support graphql-ws. This migration guide shows how to reenable subscriptions-transport-ws to make your transition from Apollo Server 2 to Apollo Server 3 as easy as possible, but we recommend that you then migrate from subscriptions-transport-ws to graphql-ws once you've finished the upgrade. The subscriptions documentation shows how to use the newer graphql-ws rather than the unmaintained subscriptions-transport-ws.

Reenabling subscriptions

Currently, these instructions are only for Apollo Server's Express integration. It is likely possible to integrate subscriptions-transport-ws with other integrations. PRs to this migration guide are certainly welcome!

In Apollo Server 3, you can't use subscriptions-transport-ws with the "batteries-included" apollo-server package. If your project uses apollo-server with subscriptions, first swap to apollo-server-express.

Then, complete the following:

  1. Install subscriptions-transport-ws and @graphql-tools/schema.

    npm install subscriptions-transport-ws @graphql-tools/schema
  2. Add the following imports in the module where your Apollo Server is currently instantiated. We'll use these in the subsequent steps.

    import { createServer } from 'http';
    import { execute, subscribe } from 'graphql';
    import { SubscriptionServer } from 'subscriptions-transport-ws';
    import { makeExecutableSchema } from '@graphql-tools/schema';
  3. Create an http.Server instance with your Express app.

    In order to set up both the HTTP and WebSocket servers, we'll need to create an http.Server. Do this by passing your Express app to the createServer function which we imported from the Node.js http module.

    // This `app` is the returned value from `express()`.
    const httpServer = createServer(app);
  4. Create an instance of GraphQLSchema (if one doesn't already exist).

    Your server may already pass a schema to the ApolloServer constructor. If it does, this step can be skipped. You'll use the existing schema instance in a later step.

    The SubscriptionServer (which we'll instantiate next) doesn't accept typeDefs and resolvers directly, but rather an executable GraphQLSchema. We can pass this schema object to both the SubscriptionServer and ApolloServer. This way, it's clear that the same schema is being used in both places.

    const schema = makeExecutableSchema({ typeDefs, resolvers });
    // ...
    const server = new ApolloServer({
  5. Create the SubscriptionServer.

    const subscriptionServer = SubscriptionServer.create({
    // This is the `schema` we just created.
    // These are imported from `graphql`.
    // This `server` is the instance returned from `new ApolloServer`.
    // Ensures the same graphql validation rules are applied to both the Subscription Server and the ApolloServer
    validationRules: server.requestOptions.validationRules
    // Providing `onConnect` is the `SubscriptionServer` equivalent to the
    // `context` function in `ApolloServer`. Please [see the docs](
    // for more information on this hook.
    async onConnect(
    connectionParams: Object,
    webSocket: WebSocket,
    context: ConnectionContext
    ) {
    // If an object is returned here, it will be passed as the `context`
    // argument to your subscription resolvers.
    }, {
    // This is the `httpServer` we created in a previous step.
    server: httpServer,
    // This `server` is the instance returned from `new ApolloServer`.
    path: server.graphqlPath,
  6. Add a plugin to your ApolloServer constructor to close the SubscriptionServer.

    const server = new ApolloServer({
    plugins: [{
    async serverWillStart() {
    return {
    async drainServer() {
  7. Finally, adjust the existing listen.

    Most applications will be calling app.listen(...) on their Express app. This should be changed to httpServer.listen(...) using the same . This will begin listening on the HTTP and WebSocket transports simultaneously.

A completed example of migrating subscriptions is shown below:

File uploads

Apollo Server 2 provides built-in support for file uploads via an outdated version of the graphql-upload library. Using an updated version of graphql-upload requires you to disable this built-in support due to backward incompatible changes.

This built-in support is removed in Apollo Server 3. To use graphql-upload, you can choose an appropriate version and integrate it yourself. Note that graphql-upload does not support federation or every Node.js framework supported by Apollo Server.

To use graphql-upload with Apollo Server 3, see the documentation on enabling file uploads in Apollo Server. Note that if your project currently uses uploads with the "batteries-included" apollo-server package, you must first swap to apollo-server-express.

Warning: using graphql-upload in your server without proper mitigation increases your server's vulnerability to Cross-Site Request Forgery (CSRF) attacks. This affected the default configuration of Apollo Server 2 and will affect you if you manually integrate with graphql-upload. We do not typically recommend the use of graphql-upload and think that file uploads generally work best out of band from your GraphQL API; however, if you do want to use graphql-upload then you must take actions to protect your users from CSRF attacks. The easiest way to do this is to ensure you are using at least Apollo Server 3.7 and enable our CSRF prevention feature by passing csrfPrevention: true to new ApolloServer(). Note that you will have to configure your upload client (like apollo-upload-client) to pass a non-empty Apollo-Require-Preflight header to your server once this feature is enabled.

Additionally, Apollo Server 3 no longer re-exports the GraphQLUpload symbol from the graphql-upload package.

GraphQL Playground

By default, Apollo Server 2 serves the (now-retired) GraphQL Playground IDE from its base URL in non-production environments. In production, it serves no landing page. Apollo Server 2 also accepts a playground constructor option to override this default behavior (for example, to enable GraphQL Playground even in production).

Apollo Server 3 removes GraphQL Playground (and its associated constructor option) in favor of a landing page that links to in non-production environments. In production, it serves a simplified landing page instead.

You can customize your Apollo Server 3 landing page, as described in Build and run queries.

Reenabling GraphQL Playground

You can continue to use GraphQL Playground by installing its associated plugin. You customize its behavior by passing options to the plugin instead of via the playground constructor option.

If your code did not specify the playground constructor option and you'd like to keep the previous behavior instead of trying the new splash page, you can do that as follows:

import { ApolloServerPluginLandingPageGraphQLPlayground,
ApolloServerPluginLandingPageDisabled } from 'apollo-server-core';
new ApolloServer({
plugins: [
process.env.NODE_ENV === 'production'
? ApolloServerPluginLandingPageDisabled()
: ApolloServerPluginLandingPageGraphQLPlayground(),

If your code passed new ApolloServer({playground: true}), you can keep the previous behavior with:

import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
new ApolloServer({
plugins: [

If your code passed new ApolloServer({playground: false}) you can keep the previous behavior with:

import { ApolloServerPluginLandingPageDisabled } from 'apollo-server-core';
new ApolloServer({
plugins: [

If your code passed an options object like new ApolloServer({playground: playgroundOptions}), you can keep the previous behavior with:

import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
new ApolloServer({
plugins: [

Additional changes to GraphQL Playground

Specifying an endpoint

In Apollo Server 2, the default value of GraphQL Playground's endpoint option is determined in different ways by different Node.js framework integrations. In many cases, it's necessary to manually specify playground: {endpoint}.

In Apollo Server 3, the default endpoint used by GraphQL Playground is the browser's current URL. In many cases, this means that you don't have to specify endpoint anymore. If your Apollo Server 2 app specified playground: {endpoint} (and you wish to continue using GraphQL Playground), try removing endpoint from the options passed to ApolloServerPluginLandingPageGraphQLPlayground and see if it works for you.

Specifying settings

In Apollo Server 2, the behavior of the settings GraphQL Playground option can be surprising. If you don't explicitly pass {playground: {settings: {...}}} then GraphQL Playground always uses settings that are built into its React application (some of which can be adjusted by the user in their browser). However, if you pass any object as playground: {settings: {...}}, several default value overrides take effect.

This surprising behavior is removed in Apollo Server 3. All settings use default values from the GraphQL Playground React app if they aren't specified in the settings option to ApolloServerPluginLandingPageGraphQLPlayground.

If your app does pass in playground: {settings: {...}} and you want to make sure the settings used in your GraphQL Playground do not change, you should copy any relevant settings from the Apollo Server 2 code into your app.

For example, you could replace:

new ApolloServer({playground: {settings: {'some.setting': true}}})


import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
new ApolloServer({
plugins: [
settings: {
'some.setting': true,
'general.betaUpdates': false,
'editor.theme': 'dark',
'editor.cursorShape': 'line',
'editor.reuseHeaders': true,
'tracing.hideTracingResponse': true,
'queryPlan.hideQueryPlanResponse': true,
'editor.fontSize': 14,
'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,
'request.credentials': 'omit',

Removed constructor options

The following ApolloServer constructor options have been removed in favor of other features or configuration methods.


Apollo Server 3 removes support for the graphql-extensions API, which was used to extend Apollo Server's functionality. This API has numerous limitations relative to the plugins API introduced in Apollo Server v2.2.0.

Unlike graphql-extensions, the plugins API enables cross-request state, and its hooks virtually all interact with the same GraphQLRequestContext object.

If you've written your own (passed to new ApolloServer({extensions: ...})), you should rewrite them as plugins before upgrading to Apollo Server 3.


"Engine" is a previous name of Apollo Studio. Prior to Apollo Server v2.18.0, you passed the engine constructor option to configure how Apollo Server communicates with Studio. Additionally, you could specifying your Apollo API key with the ENGINE_API_KEY environment variable, and you could specify a with the ENGINE_SCHEMA_TAG environment variable.

In later versions of Apollo Server (including Apollo Server 3), you instead provide this configuration via a combination of the apollo constructor option, plugins, and APOLLO_-prefixed environment variables. Apollo Server 3 does not support the engine constructor option or the ENGINE_-prefixed environment variables.

If your project still uses the engine option or the ENGINE_-prefixed environment variables, see Migrating from the engine option before upgrading to Apollo Server 3.

Note that if you are using Studio, make sure to set your graph ref or graph ID.


In Apollo Server 2, you can pass schemaDirectives to new ApolloServer alongside typeDefs and resolvers. These arguments are all passed through to the makeExecutableSchema function from the graphql-tools package.

The graphql-tools project deprecated the schemaDirectives feature and removed it in v8 of @graphql-tools/schema.

In Apollo Server 3, the ApolloServer constructor now only passes typeDefs, resolvers, and parseOptions through to makeExecutableSchema.

If you would like to still use schemaDirectives, you can install an older version of @graphql-tools/schema yourself with npm install @graphql-tools/schema@7 and call makeExecutableSchema yourself and pass its returned schema as the schema constructor option.

That is, you can replace:

new ApolloServer({


// Make sure you are using v7, not anything newer!
import { makeExecutableSchema } from '@graphql-tools/schema';
new ApolloServer({
schema: makeExecutableSchema({

This can help you with the initial migration to Apollo Server 3. As schemaDirectives is no longer part of the actively maintained version of @graphql-tools/schema, we recommend that once you've successfully migrated to Apollo Server 3, you then port your schema to the newer schema directives API which uses the mapSchema function in @graphql-tools/utils.

In Apollo Server 2, there are subtle differences between providing a schema with schema versus providing it with typeDefs and resolvers. For example, the automatic definition of the @cacheControl directive is added only in the latter case. These differences are removed in Apollo Server 3 (for example, the definition of the @cacheControl directive is never automatically added).


In Apollo Server 2, the tracing constructor option enables a trace mechanism implemented in the apollo-tracing package. This package uses a comparatively inefficient JSON format for execution traces returned via the tracing GraphQL response extension. The format is consumed only by the deprecated engineproxy and GraphQL Playground. It is not the tracing format used for Apollo Studio usage reporting or federated inline traces.

The tracing constructor option is removed in Apollo Server 3. The apollo-tracing package has been deprecated and is no longer being published.

If you rely on this deprecated trace format, you might be able to use the old version of apollo-server-tracing directly:

new ApolloServer({
plugins: [

This workaround has not been tested! If you need this to work and it doesn't, please file an issue and we will investigate a fix to enable support in Apollo Server 3.


In Apollo Server 2, cache policy support is configured via the cacheControl constructor option. There are several improvements to the semantics of cache policies in Apollo Server 3, as well as changes to how caching is configured.

The cacheControl constructor option is removed in Apollo Server 3. To customize cache control, you instead manually install the cache control plugin and provide custom options to it.

For example, if you currently provide defaultMaxAge and/or calculateHttpHeaders to cacheControl like so:

new ApolloServer({
cacheControl: {

You now provide them like so:

import { ApolloServerPluginCacheControl } from 'apollo-server-core';
new ApolloServer({
plugins: [

If you currently pass cacheControl: false like so:

new ApolloServer({
cacheControl: false,

You now install the disabling plugin like so:

import { ApolloServerPluginCacheControlDisabled } from 'apollo-server-core';
new ApolloServer({
plugins: [

In Apollo Server 2, cacheControl: true was a shorthand for setting cacheControl: {stripFormattedExtensions: false, calculateHttpHeaders: false}. If you either passed cacheControl: true or explicitly passed stripFormattedExtensions: false, Apollo Server 2 would include a cacheControl response extension inside your GraphQL response. This was used by the deprecated engineproxy server. Support for writing this response extension has been removed from Apollo Server 2. This allows for a more memory-efficient cache control plugin implementation.

In Apollo Server 2, definitions of the @cacheControl directive (and the CacheControlScope enum that it uses) were sometimes automatically inserted into your schema. (Specifically, they were added if you defined your schema with the typeDefs and resolvers options, but not if you used the modules or schema options or if you were a federated gateway. Passing cacheControl: false did not stop the definitions from being inserted!) In Apollo Server 3, these definitions are never automatically inserted.

So if you use the @cacheControl directive in your schema, you should add these definitions to your schema:

enum CacheControlScope {
directive @cacheControl(
maxAge: Int
scope: CacheControlScope
inheritMaxAge: Boolean

(You may add them to your schema in Apollo Server 2 before upgrading if you'd like.)

In Apollo Server 2, plugins that want to change the 's overall cache policy can overwrite the requestContext.overallCachePolicy. In Apollo Server 3, that field is considered read-only, but it does have new methods to mutate its state. So you should replace:

requestContext.overallCachePolicy = { maxAge: 100 };


requestContext.overallCachePolicy.replace({ maxAge: 100 });

(You may also want to consider using restrict instead of replace; this method only allows maxAge to be reduced and only allows scope to change from PUBLIC to PRIVATE.)

In Apollo Server 2, returning a union type are treated similarly to fields returning a type: @cacheControl on the type itself is ignored, and maxAge if unspecified is inherited from its parent in the operation (unless it is a root field) instead of defaulting to defaultMaxAge (which itself defaults to 0). In Apollo Server 3, fields returning a union type are treated similarly to fields returning an interface or : @cacheControl on the type itself is honored, and maxAge if unspecified defaults to defaultMaxAge. If you were relying on the inheritance behavior, you can specify @cacheControl(maxAge: ...) explicitly on your union types or union-returning fields, or you can use the new @cacheControl(inheritMaxAge: true) feature on the union-returning field to restore the Apollo Server 2 behavior. If your schema contained union SomeUnion @cacheControl(...), that directive will start having an effect when you upgrade to Apollo Server 3.

In Apollo Server 2, the @cacheControl is honored on type definitions but not on type extensions. That is, if you write type SomeType @cacheControl(maxAge: 123) it takes effect but if you write extend type SomeType @cacheControl(maxAge: 123) it does not take effect. In Apollo Server 3, @cacheControl is honored on object, interface, and union extensions. If your schema accidentally contained @cacheControl on an extend, that directive will start having an effect when you upgrade to Apollo Server 3.

In Apollo Server 2, most of the logic related to cache control lives in the apollo-cache-control package. This package exports a function called plugin as well as TypeScript types CacheControlFormat, CacheHint, CacheScope, and CacheControlExtensionOptions. In Apollo Server 3, this logic lives in apollo-server-core and the apollo-cache-control package is no longer published. apollo-server-core exports ApolloServerPluginCacheControl and ApolloServerPluginCacheControlOptions (which are similar to plugin and CacheControlExtensionOptions). apollo-server-types exports CacheHint and CacheScope. There is no equivalent export to CacheControlFormat because as described above, the code to write the cacheControl response extension is not part of Apollo Server 3.


See GraphQL Playground.

Removed exports

In Apollo Server 2, apollo-server and framework integration packages such as apollo-server-express import many symbols from third-party packages and re-export them. This effectively ties the API of Apollo Server to a specific version of those third-party packages and makes it challenging to upgrade to a newer version or for you to upgrade those packages yourself.

In Apollo Server 3, most of these "re-exports" are removed. If you want to use these exports, you should import them directly from their originating package.

Exports from graphql-tools

Apollo Server 2 exports every symbol exported by graphql-tools v4. If you're importing any of the following symbols from an Apollo Server package, you should instead run npm install graphql-tools@4.x and import the symbol from graphql-tools.

Alternatively, read the GraphQL Tools docs and find out which @graphql-tools/subpackage the symbol is exported from in more modern versions of GraphQL Tools.

  • AddArgumentsAsVariables
  • AddTypenameToAbstract
  • CheckResultAndHandleErrors
  • DirectiveResolverFn
  • ExpandAbstractTypes
  • ExtractField
  • FilterRootFields
  • FilterToSchema
  • FilterTypes
  • GraphQLParseOptions
  • IAddResolveFunctionsToSchemaOptions
  • IConnectorCls
  • IConnectorFn
  • IConnector
  • IConnectors
  • IDelegateToSchemaOptions
  • IDirectiveResolvers
  • IEnumResolver
  • IExecutableSchemaDefinition
  • IFieldIteratorFn
  • IFieldResolver
  • IGraphQLToolsResolveInfo
  • ILogger
  • IMockFn
  • IMockOptions
  • IMockServer
  • IMockTypeFn
  • IMocks
  • IResolverObject
  • IResolverOptions
  • IResolverValidationOptions
  • IResolversParameter
  • IResolvers
  • ITypeDefinitions
  • ITypedef
  • MergeInfo
  • MergeTypeCandidate
  • MockList
  • NextResolverFn
  • Operation
  • RenameRootFields
  • RenameTypes
  • ReplaceFieldWithFragment
  • Request
  • ResolveType
  • Result
  • SchemaDirectiveVisitor
  • SchemaError
  • TransformRootFields
  • Transform
  • TypeWithResolvers
  • UnitOrList
  • VisitTypeResult
  • VisitType
  • WrapQuery
  • addCatchUndefinedToSchema
  • addErrorLoggingToSchema
  • addMockFunctionsToSchema
  • addResolveFunctionsToSchema
  • addSchemaLevelResolveFunction
  • assertResolveFunctionsPresent
  • attachConnectorsToContext
  • attachDirectiveResolvers
  • buildSchemaFromTypeDefinitions
  • chainResolvers
  • checkForResolveTypeResolver
  • concatenateTypeDefs
  • decorateWithLogger
  • defaultCreateRemoteResolver
  • defaultMergedResolver
  • delegateToSchema
  • extendResolversFromInterfaces
  • extractExtensionDefinitions
  • forEachField
  • introspectSchema
  • makeExecutableSchema
  • makeRemoteExecutableSchema
  • mergeSchemas
  • mockServer
  • transformSchema

Exports from graphql-subscriptions

Apollo Server 2 exports every symbol exported by graphql-subscriptions. If you are importing any of the following symbols from an Apollo Server package, you should instead run npm install graphql-subscriptions and import the symbol from graphql-subscriptions instead.

  • FilterFn
  • PubSub
  • PubSubEngine
  • PubSubOptions
  • ResolverFn
  • withFilter

Exports from graphql-upload

Apollo Server 2 exports the GraphQLUpload symbol from (our fork of) graphql-upload. Apollo Server 3 no longer has built-in graphql-upload integration. See the documentation on how to enable file uploads in Apollo Server 3.

Apollo Server 2 exports a defaultPlaygroundOptions object, along with PlaygroundConfig and PlaygroundRenderPageOptions types to support the playground top-level constructor .

In Apollo Server 3, GraphQL Playground is one of several landing pages implemented via plugins, and there are no default options for it. The ApolloServerPluginLandingPageGraphQLPlaygroundOptions type exported from apollo-server-core plays a similar role to PlaygroundConfig and PlaygroundRenderPageOptions. See the section on playground above for more details on configuring GraphQL Playground in Apollo Server 3.

Removed features

Several small features have been removed from Apollo Server 3.

Guessing Apollo Studio graph ID from API key

In Apollo Server 2, if you specify an Apollo API key (e.g., with the APOLLO_KEY environment variable) that starts with service:your-graph-id:, Apollo Server automatically guesses that your Studio graph ID (used for usage reporting, schema reporting, , and so on) is your-graph-id.

In Apollo Server 3, you should instead specify your Studio graph ID explicitly when using features that connect to Studio. You can specify the graph ID by itself in the APOLLO_GRAPH_ID environment variable (or via new ApolloServer({apollo: {graphId}})), or alongside the variant in a string like your-graph-id@your-graph-variant in the APOLLO_GRAPH_REF environment variable (or via new ApolloServer({apollo: {graphRef}})). See the apollo constructor option for more details.

If you set your API key but do not set your or ID:

  • If you explicitly set up features like the usage reporting plugin or managed federation, Apollo Server 3 throws an error on startup.
  • If you don't explicitly set up or disable the usage reporting plugin, Apollo Server 3 logs a warning suggesting that you either set your graph ref or disable the usage reporting plugin.

ApolloServer.schema field

Apollo Server 2 has a deprecated field ApolloServer.schema (which doesn't work when the server is a federated gateway). Apollo Server 3 does not contain this field. To access your server's schema, you have a few options:

  • Construct the schema yourself (e.g., with const schema = makeExecutableSchema({typeDefs, resolvers}) from @graphql-tools/schema), pass it in as new ApolloServer({schema}), and refer to this same schema value elsewhere. Apollo Server 3 won't modify the schema you pass in.
  • Write a plugin that uses the serverWillStart event to obtain the schema. Note that if your server is a gateway, this receives the first schema that's loaded on startup but doesn't receive any subsequent schemas if it updates dynamically.
  • If your server is a gateway, register a callback with gateway.onSchemaChange. Note that this API has some inconsistent behavior. To resolve this, we are considering adding an Apollo Server plugin event that receives all schema updates.


Apollo Server 2 contains a package apollo-server-testing. This package is a thin wrapper around the server.executeOperation method. As of Apollo Server 2.25.0, we no longer document this package and instead document using executeOperation directly.

In Apollo Server 3, we no longer publish this package. It's possible that the Apollo Server 2 version of this package might work with Apollo Server 3 servers. If you do use apollo-server-testing, we suggest that you migrate to using executeOperation directly (with at least 2.25.0 of Apollo Server) before upgrading to Apollo Server 3.

If you used apollo-server-testing like so:

const { createTestClient } = require('apollo-server-testing');
const { query, mutate } = createTestClient(server);
await query({ query: QUERY });
await mutate({ mutation: MUTATION });

then you can use executeOperation like so:

await server.executeOperation({ query: QUERY });
await server.executeOperation({ query: MUTATION });

Note that prior to Apollo Server 2.25.0, apollo-server-testing functions allowed you to pass your operation as a string or a DocumentNode as returned from gql, but executeOperation only supported strings. As of v2.25.0, executeOperation takes string or DocumentNode, which makes the change shown above straightforward in all cases.

apollo-datasource-rest: baseURL override change

When you create a RESTDataSource subclass, you need to provide its baseURL. This can be done via this.baseURL = ... in the constructor or resolveURL, or via baseURL = ... at the class level.

In Apollo Server 2 you can also provide baseURL via a getter like get baseURL() { ... }. Apollo Server 3 is compiled with TypeScript 4, which no longer supports overriding a property with an accessor, so this is no longer allowed.

If you used a getter in order to provide a dynamic URL, like this:

class MyDataSource extends RESTDataSource {
get baseURL() {
return someDynamicallyCalculatedURL();

you can instead override resolveURL:

class MyDataSource extends RESTDataSource {
async resolveURL(request: RequestOptions) {
if (!this.baseURL) {
this.baseURL = someDynamicallyCalculatedURL();
return super.resolveURL(request);

apollo-server-env's global type definitions

Global TypeScript definitions have been removed from apollo-server-env since they conflicted with similar global types provided by @types/supertest, which we use in Apollo Server's test suite. These removed types include types which resemble those of the Fetch API including, e.g., fetch, RequestInfo, Headers, Request, Response, ResponseInit, and more. See the full list prior to removal here.

We don't expect this to affect many users and we have not publicly suggested using these types in the past, but it's possible that implementations may be using them inadvertently (e.g., from auto-import on apollo-server-env). While other type definition sets provide similar hand-curated types, for the time being, we have chosen to rely the same-named types from TypeScript's lib.dom.d.ts — e.g., its RequestInfo type definition. Even if those types are more appropriate for browsers, they're a reliable and well-maintained source. For more details, including our plan for adjusting this again in the future, see PR #5165.

Changed features

Plugin API

Almost all plugin events are now async

In Apollo Server 2, some plugin events are synchronous (their return value is not a Promise), and some are "maybe-asynchronous" (they could return a Promise if they wanted, but didn't have to). This means that you can't do asynchronous work in the former events, and the typings for the latter events are somewhat complex.

In Apollo Server 3, almost all plugin methods are always asynchronous: they always return a Promise type. This includes end hooks as well. The exceptions are willResolveField and its end hook and schemaDidLoadOrUpdate, which are always synchronous.

In practice, this means that all of your plugin events should use async functions or methods. If you are using TypeScript, you need to do this for your code to compile.

In practice, Apollo Server generally uses await or Promise.all on values returned by plugin methods instead of an explicit .then. These constructs accept both Promises and normal values. If you aren't using TypeScript, you might be able to get away with synchronous plugin methods for the time being.

willSendResponse is called more consistently

In Apollo Server 2, some errors related to persisted queries invoke the requestDidStart and didEncounterError plugin events without invoking the willSendResponse event afterwards.

In Apollo Server 3, any request that makes it far enough to invoke requestDidStart also invokes willSendResponse. See this PR for details.

executionDidStart can no longer return a function

In Apollo Server 2, the executionDidStart plugin event could return nothing, an object, or a function. If a function was provided, it would be called when execution finished (one might imagine an executionDidEnd).

In Apollo Server 3, executionDidStart must return either nothing or an object. If you previously returned a function, you can now return an object with an executionDidEnd field like so:

const server = new ApolloServer({
plugins: [{
async requestDidStart() {
return {
async executionDidStart() {
return {
async executionDidEnd() {
// your code here

For more information, you can check out the related plugin documentation on end hooks.

Gateway interface renamed and simplified

In Apollo Server 2, the TypeScript type used for the gateway constructor option is called GraphQLService. In Apollo Server 3, it's called GatewayInterface. (For now, an identical interface named GraphQLService continues to be exported.)

This interface now requires the following:

  • The stop method must be present.
  • The executor method must async
  • The apollo option must always be passed to the load method.

All recent versions of @apollo/gateway satisfy these stronger requirements.

Bad request errors more consistently return 4xx

In Apollo Server 2, certain poorly formatted requests receive HTTP responses with a 5xx status (indicating a server error) instead of 4xx (indicating a client error). For example, this occurs in some integrations for a missing POST body or a JSON parse error.

In Apollo Server 3, these errors are handled in a more consistent manner across integrations and consistently return HTTP responses with a 4xx status.

Extensions (custom details) on ApolloError

Initializing an error

In Apollo Server 2, error extensions can be passed to the ApolloError constructor either as the third argument, or as an extensions option on the third argument.

In other words, these two lines are equivalent with Apollo Server 2:

new ApolloError(message, code, {key: 'value'})
new ApolloError(message, code, {extensions: {key: 'value'}})

In Apollo Server 3, only the first line above is supported. If you try to use the second line, the constructor throws an error. Before upgrading, replace any code using the second form with the first form.

Reading error extensions

In Apollo Server 2, an error extension (foo) is present in two locations on an ApolloError object (error): and When that object is serialized for a JSON response, the extension is also present in two locations in the JSON: and

In Apollo Server 3, an error extension is present in one ApolloError location: When the error is serialized to JSON, the extension is present only in

See this PR for more details.


In Apollo Server 2, the mocks and mockEntireSchema constructor options are essentially implemented as follows:

import { addMockFunctionsToSchema } from 'graphql-tools'; // v4.x
const { mocks, mockEntireSchema } = constructorOptions;
const schemaWithMocks = addMockFunctionsToSchema({
typeof mocks === 'boolean' || typeof mocks === 'undefined'
? {} : mocks,
typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,

In Apollo Server 3, we've upgraded from graphql-tools v4 to @graphql-tools/mock v8. The details of how mocking works in GraphQL Tools has changed, and the name of the function that implements it has changed too. The new implementation is as follows:

import { addMocksToSchema } from '@graphql-tools/mock'; // v8.x
const { mocks, mockEntireSchema } = constructorOptions;
const schemaWithMocks = addMocksToSchema({
mocks: mocks === true || typeof mocks === 'undefined' ? {} : mocks,
typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,

So the name of the function has changed, and additionally, the semantics of the function have changed. You can read the GraphQL Tools migration docs to see if you use any mocking features that have changed between v4 and v8, and adjust the value of your mocks argument if so.

To use features of addMocksToSchema that require passing more options to it than the three that ApolloServer passes through, you can use the library directly. To do so, run npm install @graphql-tools/mock @graphql-tools/schema in your app, and replace

new ApolloServer({ typeDefs, resolvers, mocks });


import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
new ApolloServer({
schema: addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
mocks: // ...
// ...

Alternatively, to keep the current behavior without changing your mock functions at all, you can continue to use graphql-tools v4. To do this, run npm install graphql-tools@v4.x in your app, and replace

new ApolloServer({typeDefs, resolvers, mocks, preserveResolvers});


import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
new ApolloServer({
schema: addMockFunctionsToSchema({
schema: makeExecutableSchema({typeDefs, resolvers}),
typeof mockEntireSchema === 'undefined' ? false : !mockEntireSchema,

apollo-server-caching test suite helpers

In Apollo Server 2, the apollo-server-caching package exports functions like testKeyValueCache, testKeyValueCache_Basic, and testKeyValueCache_Expiration, which define Jest test suites. The package also exports a TestableKeyValueCache type that's required for these test suites to be able to flush and close the cache.

You can use this to define Jest test suites for your own implementation of the KeyValueCache interface, although this requires intricate Jest and TypeScript config to set up.

In Apollo Server 3, these functions and type are removed. Instead, a runKeyValueCacheTests function is exported which can be run in any test suite (without any Jest-specific behavior). See the apollo-server-caching README for more details.

Changes to framework integrations

start() now mandatory for non-serverless framework integrations

Apollo Server v2.22 introduced the server.start() method for non- framework integrations (Express, Fastify, Hapi, Koa, Micro, and Cloudflare). Users of these integrations were encouraged to await a call to it immediately after creating an ApolloServer object:

async function startServer() {
const server = new ApolloServer({...});
await server.start();
server.applyMiddleware({ app });

If any plugin serverWillStart events throw, or if the server fails to load its schema properly (for example, if the server is a gateway and cannot load its schema), then await server.start() will throw. This allows you to ensure that Apollo Server has successfully loaded its configuration before you start listening for HTTP requests.

In Apollo Server 2, calling this method was optional. If you didn't call it, you could still call methods like applyMiddleware and start listening. If a startup failure occurred, all GraphQL requests would fail.

In Apollo Server 3, you must await server.start() immediately after new ApolloServer before calling applyMiddleware (or getMiddleware or getHandler, depending on the integration). Note that you don't have to call it before calling the testing method executeOperation. Otherwise, that method will throw.

This does not apply to the batteries-included apollo-server package (the server is started as part of the async method server.listen()) or to serverless framework integrations.

Peer deps instead of direct deps

In Apollo Server 2, the apollo-server-express, apollo-server-koa, and apollo-server-micro packages have direct dependencies on express, koa, and micro respectively, and apollo-server-fastify and apollo-server-hapi have no dependency on fastify, hapi, or @hapi/hapi.

In Apollo Server 3, these packages have peer dependencies on their corresponding framework packages. This means that you need to install a version of that package of your choice yourself in your app (though most likely that was already the case).

CORS * is now the default

In Apollo Server 2, the default CORS configuration for most packages is to serve an access-control-allow-origin: * header for all responses.

However, this is not the case in Apollo Server 2 for apollo-server-lambda, apollo-server-cloud-functions, or apollo-server-azure-functions (these serve no CORS headers by default), or for apollo-server-koa (this serves an access-control-allow-origin header with a value matching the request's origin by default).

In Apollo Server 3, all implementations of ApolloServer that support CORS configuration have the same default of access-control-allow-origin: *.

To use your framework's Apollo Server 2 default CORS behavior in your Apollo Server 3 application, provide the options specified below to the createHandler/getMiddleware method.

Lambda / Cloud Functions

server.createHandler({expressGetMiddlewareOptions: {cors: false}})

Azure Functions

server.createHandler({cors: false})


server.getMiddleware({cors: {}})

apollo-server-micro and apollo-server-cloudflare do not support CORS configuration in Apollo Server 2 or 3.


In Apollo Server 2, apollo-server-express officially supports both the express framework and the older connect framework.

In Apollo Server 3, we no longer guarantee future support for connect. We do still run a test suite against it and we will try not to unintentionally break functionality under connect, but if future changes are easier to implement in an Express-only fashion, we reserve the right to break connect compatibility within Apollo Server 3.


Now based on Express

In Apollo Server 2, apollo-server-lambda implements parsing AWS Lambda events and performing standard web framework logic inside the package itself. As Lambda added new capabilities, the logic had to adapt, and as Apollo Server added new features, they had to be implemented from scratch in apollo-server-lambda. Additionally, there was no way to add additional HTTP functionality (e.g., "middleware") to apollo-server-lambda.

In Apollo Server 3, apollo-server-lambda is implemented as a wrapper around apollo-server-express, using an actively maintained package to parse AWS Lambda events into Express requests. This simplifies the implementation and enables us to support new Lambda integrations such as Application Load Balancers. Additionally, because it now uses Express internally, you can extend HTTP functionality via Express middleware using the new expressAppFromMiddleware option to createHandler.

createHandler arguments changed

As part of the update described above, the arguments to createHandler have changed. Instead of taking only cors and onHealthCheck, the method takes an expressGetMiddlewareOptions option, which is an object that supports any of the options that the apollo-server-express applyMiddleware and getMiddleware can take (other than app).

For example, instead of:


you now write

server.createHandler({expressGetMiddlewareOptions: {onHealthCheck}})`

Handler is always async

In Apollo Server 2, the handler returned by createHandler can be called either as a function that takes a callback as a third argument, or (starting with Apollo Server v2.21.2) as an async function that returns a Promise.

In Apollo Server 3, the handler returned by createHandler is always an async function that returns a Promise. Any callback passed in will be ignored.

All currently supported Lambda runtimes support async handlers. However, if you are currently wrapping the handler returned from createHandler in your own larger handler and passing a callback into it, this no longer works. Rewrite your outer handler to be an async function and await the Promise returned from the handler returned by createHandler.

For example, if your server looked like this:

const apolloHandler = server.createHandler();
exports.handler = function (event, context, callback) {
apolloHandler(event, context, (error, result) => {
callback(error, result);

you can change it to look like this:

const apolloHandler = server.createHandler();
exports.handler = async function (event, context) {
try {
return await apolloHandler(event, context);
} finally {


In Apollo Server 2, apollo-server-cloud-functions implements standard web framework logic inside the package itself. Even though the Node.js API for Google Cloud Functions provides its request and response in the form of Express request and response objects, Apollo Server 2 has a bespoke implementation unrelated to apollo-server-express and does not support all features supported by apollo-server-express. Additionally, there is no way to add additional HTTP functionality (e.g., "middleware") to apollo-server-cloud-functions.

In Apollo Server 3, apollo-server-cloud-functions is implemented on top of apollo-server-express and supports all features supported by apollo-server-express. You can add additional HTTP functionality via Express middleware using the new expressAppFromMiddleware option to createHandler.

As part of this, the arguments to createHandler have changed. Instead of taking only cors, it takes an expressGetMiddlewareOptions option, which is an object taking any of the options that the apollo-server-express applyMiddleware and getMiddleware can take (other than app). For example, instead of:


you now write:

server.createHandler({expressGetMiddlewareOptions: {cors}})


In Apollo Server 2, apollo-server-fastify supports Fastify v2 and does not support Fastify v3. There is no dependency or peer dependency making this requirement clear.

In Apollo Server 3, apollo-server-fastify supports Fastify v3. It has not been tested with versions of Fastify older than v3. There is a peer dependency on fastify v3.


We are not certain exactly which versions of Hapi are supported by apollo-server-hapi in Apollo Server 2. We are certain that only @hapi/hapi v20.1.2 and higher are supported by Node 16, and Apollo Server 2 was not tested with versions of hapi newer than v17.8.5. (Hapi's package name changed from hapi to @hapi/hapi between v18.1.0 and v18.2.0.)

In Apollo Server 3, apollo-server-hapi works with @hapi/hapi v20.1.2 and Node 16. It is not tested with older versions of Hapi. Note that the Hapi project believes that all versions older than v20 have security issues.

Choosing which package to use
Schema basics
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy