A fully-featured caching GraphQL client.
Last release 6 days ago
21 Aug 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
27 versions withdrawn
withdrawn after publishing
7 years old
723 releases · first in 2019
Release timeline
723 releases since 2019Releases
- 4.0.12-beta.023 Dec 2025pre-release
- 4.0.1116 Dec 2025
Release notes3 sources agree
Open source →Patch Changes
-
#13050
8020829Thanks @phryneas! - Replace usage offindLastwith more backwards-compatible methods. -
#13049
05638deThanks @phryneas! - Fixes an issue where queries starting withskipTokenor lazy queries fromuseLazyQuerywere included inclient.refetchQueries()before they had been executed for the first time. While generally queries with astandbyfetchPolicyshould be included in refetch, these queries never hadvariablespassed in, so they should be excluded until they have run once and received their actual variables.These queries are now properly excluded from refetch operations until after their initial execution.
This change adds a new hidden option to
client.watchQuery,[variablesUnknownSymbol], which may be settruefor queries starting with afetchPolicyofstandby. It will only be applied when creating theObservableQueryinstance and cannot be changed later. This flag indicates that the query's variables are not yet known, and thus it should be excluded from refetch operations until they are. This option is not meant for everyday use and is intended for framework integrations only.
-
- 4.0.1010 Dec 2025
Release notes3 sources agree
Open source → - 4.0.931 Oct 2025
Release notes3 sources agree
Open source →Patch Changes
- #12993
8f3bc9bThanks @jerelmiller! - Fix an issue where switching from options withvariablestoskipTokenwithuseSuspenseQueryanduseBackgroundQuerywould create a newObservableQuery. This could cause unintended refetches wherevariableswere absent in the request when the query was referenced withrefetchQueries.
- #12993
- 4.0.827 Oct 2025
- 4.0.730 Sept 2025
Release notes3 sources agree
Open source →Patch Changes
- #12950
5b4f36aThanks @jerelmiller! - Don't sendoperationTypein the payload sent byGraphQLWsLink.
- #12950
- 4.0.625 Sept 2025
- 4.0.512 Sept 2025
- 4.0.42 Sept 2025
Release notes3 sources agree
Open source →Patch Changes
-
#12892
db8a04bThanks @jerelmiller! - Prevent unhandled rejections from the promise returned by calling themutatefunction from theuseMutationhook. -
#12899
5352c12Thanks @phryneas! - Fix an issue wheninvariantis called by external libraries when no dev error message handler is loaded. -
#12895
71f2517Thanks @jerelmiller! - SupportskipTokenwithuseQueryto provide a more type-safe way to skip query execution.import { skipToken, useQuery } from "@apollo/client/react"; // Use `skipToken` in place of `skip: true` for better type safety // for required variables const { data } = useQuery(QUERY, id ? { variables: { id } } : skipToken);Note: this change is provided as a patch within the 4.0 minor version because the changes to TypeScript validation with required variables in version 4.0 made using the
skipoption more difficult. -
#12900
c0d5be7Thanks @phryneas! - Use named exportequalinstead of default from"@wry/equality"
-
- 4.0.329 Aug 2025
- 4.0.227 Aug 2025
Release notes3 sources agree
Open source → - 4.0.125 Aug 2025
- 4.0.021 Aug 2025
Release notes
Open source →Apollo Client 4.0 Release Notes
Apollo Client 4.0 delivers a more modern, efficient, and type-safe GraphQL client experience through various architectural improvements and API refinements. This release focuses on developer experience, bundle size optimization, and framework flexibility.
Key Improvements
🎯 Framework-Agnostic Core
Apollo Client 4.0 separates React functionality from the core library, making
@apollo/clienttruly framework-agnostic. React exports now live in@apollo/client/react, allowing developers to use Apollo Client with any JavaScript framework without React dependencies.📦 Smaller Bundle Sizes
- Opt-in Local State Management: The
@clientdirective functionality is now opt-in via theLocalStateclass, reducing bundle size when not using local state - Modern Build Target: Transpiled to target
since 2023, node >= 20, not dead, leveraging modern JavaScript features for better performance - Improved Tree-Shaking: Proper
exportsfield in package.json enables better dead code elimination
💥 Unified Error Handling
Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:
ApolloErrorremoved in favor of specific error classes- Unification of errors to a single
errorproperty - Network errors now respect
errorPolicysettings - External errors passed through without wrapping
- New, more granular error classes with static
.is()methods for robust type narrowing
🔧 Enhanced TypeScript Support
- Namespaced Types: Types are now colocated with their APIs (e.g.,
useQuery.Optionsinstead ofQueryHookOptions) - Precise Return Types: Return types accurately reflect the options passed (e.g.,
returnPartialDatamakesdatatypeDeepPartial<TData>) - Stricter Type Safety: Required variables are now enforced more consistently throughout the client
- New
dataStateProperty: Enables accurate type narrowing of query results - Module Augmentation: Custom context types via declaration merging instead of fragile generics
- Customizable Type Implementations: Select types can now be customized to provide your own type implementation to seamlessly integrate with external tools such as GraphQL Codegen or
gql.tada
⚡ Modern Observable Implementation
Apollo Client 4.0 migrates from
zen-observableto RxJS, providing the industry-standard Observable implementation backed by a rich ecosystem of utilities.Major Features
Unified Error Handling
Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:
Key Changes:
ApolloErrorremoved in favor of specific error classes- Network errors now respect
errorPolicysettings - External errors passed through without wrapping
- New error classes with static
.is()methods for type checking
Error Classes:
CombinedGraphQLErrors- GraphQL errors from the serverServerError- Non-GraphQL server errorsServerParseError- Server response parsing errorsUnconventionalError- Wrapper for non-error thrown valuesLinkError- Errors from the link chain (via.is()check)
Migration Example:
// Apollo Client 3 if (error instanceof ApolloError) { console.log(error.graphQLErrors); console.log(error.networkError); } // Apollo Client 4 import { CombinedGraphQLErrors } from "@apollo/client"; if (CombinedGraphQLErrors.is(error)) { console.log(error.errors); // GraphQL errors } else if (error) { console.log(error.message); // Other errors }The
dataStatePropertyA new property that clearly indicates the completeness of query results:
Values:
empty- No data available (dataisundefined)partial- Incomplete data from cache whenreturnPartialDataistruestreaming- Incomplete data from a deferred query still streamingcomplete- Fully satisfied query result
Benefits:
- Accurate TypeScript type narrowing
- Clear loading state distinction
- Better handling of partial results
const { data, dataState } = useQuery(MY_QUERY); if (dataState === "complete") { // TypeScript knows data is fully populated console.log(data.allFields); } else if (dataState === "partial") { // TypeScript knows data might be missing fields console.log(data?.someField); }Pluggable Incremental Delivery (
@deferSupport)Apollo Client 4.0 makes incremental delivery configurable and future-proof:
import { Defer20220824Handler } from "@apollo/client/incremental"; const client = new ApolloClient({ // ... incrementalHandler: new Defer20220824Handler(), });Available Handlers:
NotImplementedHandler- Default, throws if@deferis usedDefer20220824Handler- Apollo Router format support (also aliased asGraphQL17Alpha2Handler)
Local State Management Improvements
Local state is now opt-in via the
LocalStateclass:import { LocalState } from "@apollo/client/local-state"; const client = new ApolloClient({ cache, localState: new LocalState({ resolvers: { Query: { myField: () => "Hello World", }, }, }), });Resolver Context Changes:
// Apollo Client 3 const resolver = (parent, args, context, info) => { const { cache } = context; }; // Apollo Client 4 const resolver = (parent, args, context, info) => { const { client, requestContext, phase } = context; const cache = client.cache; };React-Specific Improvements
More Predictable Hooks
useLazyQueryOverhaul:- No longer accepts
variablesorcontextoptions (pass toexecuteinstead) executefunction only acceptsvariablesandcontext- Cannot be called during render or SSR
- Automatic cancellation of in-flight queries when new ones start
useMutationChanges:- Removed
ignoreResultsoption - useclient.mutatedirectly for fire-and-forget mutations
useQueryChanges:notifyOnNetworkStatusChangenow defaults totrue- Removed deprecated
onCompletedandonErrorcallbacks
New SSR API
The new
prerenderStaticAPI replaces deprecated SSR functions:import { prerenderStatic } from "@apollo/client/react/ssr"; // Works with React 19's prerender APIs const html = await prerenderStatic(<App />, { client, });React Compiler Support
Pre-compiled React hooks optimized by the React Compiler:
// Use compiled hooks for potential performance improvements import { useQuery } from "@apollo/client/react/compiled";The compiled hooks are built with React Compiler v19.1.0-rc.2 and include a runtime polyfill for compatibility with React 17+.
Link System Evolution
All Links Now Classes
Migration from creator functions to classes:
// Apollo Client 3 import { createHttpLink, setContext } from "@apollo/client"; const httpLink = createHttpLink({ uri: "/graphql" }); const authLink = setContext((operation, prevContext) => { /*...*/ }); // Apollo Client 4 import { HttpLink, SetContextLink } from "@apollo/client"; const httpLink = new HttpLink({ uri: "/graphql" }); const authLink = new SetContextLink((prevContext, operation) => { /*...*/ });ErrorLink Changes
// Apollo Client 3 onError(({ graphQLErrors, networkError }) => { // Handle errors separately }); // Apollo Client 4 new ErrorLink(({ error }) => { if (CombinedGraphQLErrors.is(error)) { // Handle GraphQL errors } else if (error) { // Handle other errors } });Migration Tools
Automated Codemod
Apollo Client 4.0 provides a comprehensive codemod to automate migration:
# Basic usage npx @apollo/client-codemod-migrate-3-to-4 src # TypeScript projects (run separately) npx @apollo/client-codemod-migrate-3-to-4 --parser ts --extensions ts src npx @apollo/client-codemod-migrate-3-to-4 --parser tsx --extensions tsx srcThe codemod handles:
- Import updates - Moves React imports to
@apollo/client/react - Type migrations - Updates types to new namespaced locations
- Link updates - Converts creator functions to classes
- Removed exports - Moves to
@apollo/client/v4-migrationwith migration instructions
Breaking Changes Summary
Installation
# RxJS is now a peer dependency npm install @apollo/client graphql rxjsApolloClient Constructor
linkoption is now required (no more implicitHttpLinkcreation)uri,headers,credentialsremoved - useHttpLinkdirectlynameandversionmoved toclientAwarenessoptionresolversmoved toLocalStateconstructorconnectToDevToolsreplaced withdevtools.enableddisableNetworkFetchesrenamed toprioritizeCacheValues
Type System
- Removed
TContextandTCacheShapegenerics - Types moved to namespaces (see migration guide for full list)
- Custom context via module augmentation
Observable Changes
- Requires calling
.pipe()for transformations - Use RxJS operators instead of method chaining
Testing
MockedProvidernow has realistic delays by default (20-50ms)createMockClientremoved - useMockLinkdirectly
Performance & Build Improvements
- Modern JavaScript: No downlevel transpilation for modern features
- No Polyfills: Cleaner bundles, bring your own if needed
- Development Mode: Controlled via export conditions, not global
__DEV__ - ESM Support: Proper
exportsfield for better module resolution - Source Maps: Fixed and improved for better debugging
Deprecations & Removals
Removed Packages/Exports
- React render prop components (
@apollo/client/react/components) - Higher-order components (
@apollo/client/react/hoc) @apollo/client/react/parser@apollo/client/utilities/globals
Upgrade Path
- Update to Apollo Client 3.14 first for deprecation warnings
- Install peer dependencies:
npm install rxjs - Run the codemod to automate import and type updates
- Update ApolloClient initialization (explicit
HttpLink,LocalStateif needed) - Review error handling - update to use new error classes
- Test thoroughly - especially SSR, error handling, and local state
Resources
Acknowledgments
Apollo Client 4.0 represents years of community feedback and contributions. Thank you to all our contributors, early adopters, and the entire GraphQL community for making this release possible.
<details>
<summary>
Major Changes
</summary>
-
#12644
fe2f005Thanks @jerelmiller! - Replace theresultproperty onServerErrorwithbodyText.bodyTextis set to the raw string body.HttpLinkandBatchHttpLinkno longer try and parse the response body as JSON when aServerErroris thrown. -
#12673
cee90abThanks @phryneas! - TheincludeExtensionsoption ofHttpLinkandBatchHttpLinknow defaults totrue.If
includeExtensionsistrue, butextensionsis not set or empty, extensions will not be included in outgoing requests. -
#12686
dc4b1d0Thanks @jerelmiller! - A@deferquery that has not yet finished streaming is now considered loading and thus theloadingflag will betrueuntil the response has completed. A newNetworkStatus.streamingvalue has been introduced and will be set as thenetworkStatuswhile the response is streaming. -
#12539
dd0d6d6Thanks @jerelmiller! -onErrorlink now uses a singleerrorproperty to report the error that caused the link callback to be called. This will be an instance ofCombinedGraphQLErrorsin the event GraphQL errors were emitted from the terminating link,CombinedProtocolErrorsif the terminating link emitted protocol errors, or the unwrapped error type if any other non-GraphQL error was thrown or emitted.- const errorLink = onError(({ graphQLErrors, networkError, protocolErrors }) => { - graphQLErrors.forEach(error => console.log(error.message)); + const errorLink = onError(({ error }) => { + if (error.name === 'CombinedGraphQLErrors') { + error.errors.forEach(rawError => console.log(rawError.message)); + } }); -
#12586
605db8eThanks @jerelmiller! - Remove thetypeDefsoption fromApolloClient. -
#12384
6aa6fd3Thanks @jerelmiller! - Remove theasyncMaputility function. Instead use one of the RxJS operators that creates Observables from promises, such asfrom. -
#12398
8cf5077Thanks @jerelmiller! - Removes theisApolloErrorutility function to check if the error object is anApolloErrorinstance. Useinstanceofto check for more specific error types that replaceApolloError. -
#12379
ef892b4Thanks @jerelmiller! - Removes theaddTypenameoption fromInMemoryCacheandMockedProvider.__typenameis now always added to the outgoing query document when usingInMemoryCacheand cannot be disabled.If you are using
<MockedProvider />withaddTypename={false}, ensure that your mocked responses include a__typenamefield. This will ensure cache normalization kicks in and behaves more like production. -
#12396
00f3d0aThanks @jerelmiller! - Remove the deprecatederrorsproperty fromuseQueryanduseLazyQuery. Read errors from theerrorproperty instead. -
#12809
e2a0be8Thanks @jerelmiller! -operation.getContextnow returns aReadonly<OperationContext>type. -
#12809
e2a0be8Thanks @jerelmiller! - TheApolloLink.Request(i.e.GraphQLRequest) passed toApolloLink.executeno longer acceptsoperationNameandoperationTypeoptions. These properties are derived from thequeryand set on the returnedApolloLink.Operationtype. -
#12712
bbb2b61Thanks @jerelmiller! - An error is now thrown when trying to callfetchMoreon acache-onlyquery. -
#12222
d1a9054Thanks @jerelmiller! - Drop support for React 16. -
#12787
8ce31faThanks @phryneas! - RemoveDataProxynamespace and interface. -
#12450
876d070Thanks @jerelmiller! - RemoveTSerializedgeneric argument toApolloCache. TheApolloCachebase cache abstraction now returnsunknownforcache.extractwhich can be overridden by a cache subclass. -
#12614
d2851e2Thanks @jerelmiller! - ThegetCacheKeyfunction is no longer available fromoperation.getContext()in the link chain. Useoperation.client.cache.identify(obj)in the link chain instead. -
#12376
a0c996aThanks @jerelmiller! - Remove deprecatedignoreResultsoption fromuseMutation. If you don't want to synchronize component state with the mutation, useuseApolloClientto access your client instance and useclient.mutatedirectly. -
#12644
fe2f005Thanks @jerelmiller! - More strictly adhere to the GraphQL over HTTP spec. This change adds support for theapplication/graphql-response+jsonmedia type and modifies the behavior of theapplication/jsonmedia type.- The client will parse the response as a well-formed GraphQL response when the server encodes
content-typeusingapplication/graphql-response+jsonwith a non-200 status code. - The client will now throw a
ServerErrorwhen the server encodescontent-typeusingapplication/jsonand returns a non-200 status code. - The client will now throw a
ServerErrorwhen the server encodes using any othercontent-typeand returns a non-200 status code.
NOTE: If you use a testing utility to mock requests in your test, you may experience different behavior than production if your testing utility responds as
application/jsonbut your production server responds asapplication/graphql-response+json. If acontent-typeheader is not set, the client interprets the response asapplication/json. - The client will parse the response as a well-formed GraphQL response when the server encodes
-
#12600
34ff6aaThanks @jerelmiller! - Move most of the utilities in@apollo/client/utilitiesto@apollo/client/utilities/internal. Many of the utilities exported from the@apollo/client/utilitiesendpoint were not considered stable.As a result of this change, utilities or types exported from
@apollo/client/utilitiesare now documented and considered stable and will not undergo breaking changes. -
#12513
9c3207cThanks @phryneas! - Removed the@apollo/client/react/contextand@apollo/client/react/hooksentry points. Please use@apollo/client/reactinstead. -
#12384
6aa6fd3Thanks @jerelmiller! - Unusubscribing fromObservableQuerywhile a request is in flight will no longer terminate the request by unsubscribing from the link observable. -
#12463
3868df8Thanks @jerelmiller! -ObservableQuery.setOptionshas been removed as it was an alias ofreobserve. Prefer usingreobservedirectly instead.const observable = client.watchQuery(options); // Use reobserve to set new options and reevaluate the query - observable.setOptions(newOptions); + observable.reobserve(newOptions);As a result of this change,
reobservehas been marked for public use and is no longer considered an internal API. ThenewNetworkStatusargument has been removed to facilitate this change. -
#12478
5ea6a45Thanks @jerelmiller! - Removevariablesfrom the result returned fromuseSubscription. -
#12735
5159880Thanks @jerelmiller! - Remove deprecatedresultCacheMaxSizeoption fromInMemoryCacheoptions. -
#12673
cee90abThanks @phryneas! - TheApolloClientconstructor optionsnameandversionthat are used to configure the client awareness feature have moved onto aclientAwarenesskey.const client = new ApolloClient({ // .. - name: "my-app", - version: "1.0.0", + clientAwareness: { + name: "my-app", + version: "1.0.0", + }, }); -
#12367
e6af35eThanks @jerelmiller! - ThepreviousDataproperty onuseLazyQuerywill now change only whendatachanges. PreviouslypreviousDatawould change to the same value asdatawhile the query was loading. -
#12690
5812759Thanks @phryneas! - Aliasing any other field to__typenameis now forbidden. -
#12556
c3fcedaThanks @phryneas! -ObservableQuerywill now keep previousdataaround when emitting aloadingstate, unlessqueryorvariableschanged. Note that@exportsvariables are not taken into account for this, sodatawill stay around even if they change. -
#12776
bce9b74Thanks @jerelmiller! - Report masked fragments as complete even when a nested masked fragment contains partial data. -
#12788
4179446Thanks @phryneas! -TVariablesnow alwaysextends OperationVariablesin all interfaces. -
#12224
51e6c0fThanks @jerelmiller! - Remove deprecatedpartialRefetchoption. -
#12407
8b1390bThanks @jerelmiller! - Callingrefetchwith new variables will now set thenetworkStatustorefetchinstead ofsetVariables. -
#12476
6afff60Thanks @jerelmiller! - Subscriptions now emit aSubscribeResultinstead of aFetchResult. As a result, theerrorsfield has been removed in favor oferror. -
#12457
32e85eaThanks @jerelmiller! - Network errors triggered by queries now adhere to theerrorPolicy. This means that GraphQL errors and network errors now behave the same way. Previously promise-based APIs, such asclient.query, would reject the promise with the network error even iferrorPolicywas set toignore. The promise is now resolved with theerrorproperty set to the network error instead. -
#12840
83e132aThanks @phryneas! - If you use an incremental delivery handler, you now have to explicitly opt into adding the chunk types to theApolloLink.Resulttype.import { Defer20220824Handler } from "@apollo/client/incremental"; declare module "@apollo/client" { export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {} } -
#12712
bbb2b61Thanks @jerelmiller! -cache-onlyqueries are no longer refetched when callingclient.reFetchObservableQuerieswhenincludeStandbyistrue. -
#12808
8e31a23Thanks @phryneas! - HTTP Multipart handling will now throw an error if the connection closed before the final boundary has been received. Data after the final boundary will be ignored. -
#12384
6aa6fd3Thanks @jerelmiller! - Remove theiterateObserversSafelyutility function. -
#12825
292b949Thanks @jerelmiller! - TheserializeFetchParameterhelper is no longer exported andJSON.stringifyis used directly. As such, theClientParseErrortype has also been removed in favor of throwing any JSON serialize errors directly. -
#12595
60bb49cThanks @jerelmiller! - Remove the@apollo/client/testing/experimentaltest utilities. Use GraphQL Testing Library instead. -
#12718
ecfc02aThanks @jerelmiller! - Version bump only to release latest asrc. -
#12470
d32902fThanks @phryneas! -ssrMode,ssrForceFetchDelayanddisableNetworkFetcheshave been reworked:Previously, a
ObservableQuerycreated byclient.queryorclient.watchQuerywhile one of those were active would permanently be changed from afetchPolicyof"network-only"or"cache-and-network"to"cache-first", and stay that way even long afterdisableNetworkFetcheswould have been deactivated.Now, the
ObservableQuerywill keep their originalfetchPolicy, but queries made duringdisableNetworkFetcheswill just apply thefetchPolicyreplacement at request time, just for that one request.ApolloClient.disableNetworkFetcheshas been renamed toApolloClient.prioritizeCacheValuesto better reflect this behaviour. -
#12559
49ace0eThanks @jerelmiller! -ObservableQuery.variablescan now be reset back to empty when callingreobservewithvariables: undefined. Previously thevariableskey would be ignored sovariableswould remain unchanged. -
#12559
49ace0eThanks @jerelmiller! -neveris no longer supported as a validTVariablesgeneric argument for APIs that requirevariablesas part of its type. UseRecord<string, never>instead. -
#12735
5159880Thanks @jerelmiller! - Remove deprecatedconnectToDevtoolsoption fromApolloClientOptions. Usedevtools.enabledinstead. -
#12576
a92ff78Thanks @jerelmiller! - ThecacheandforceFetchproperties are no longer available on context when callingoperation.getContext().cachecan be accessed through theoperationwithoperation.client.cacheinstead.forceFetchhas been replaced withqueryDeduplicationwhich specifies whetherqueryDeduplicationwas enabled for the request or not. -
#12533
73221d8Thanks @jerelmiller! - Remove theonErrorandsetOnErrormethods fromApolloLink.onErrorwas only used byMockLinkto rewrite errors ifsetOnErrorwas used. -
#12485
d338303Thanks @jerelmiller! - Throw an error for queries and mutations if the link chain completes without emitting a value. -
#12556
c3fcedaThanks @phryneas! - RemovedgetLastResult,getLastErrorandresetLastResultsfromObservableQuery -
#12663
01512f2Thanks @jerelmiller! - Unsubscribing from anObservableQuerybefore a value has been emitted will remove the query from the tracked list of queries and will no longer be eligible for query deduplication. -
#12809
e2a0be8Thanks @jerelmiller! -operation.operationTypeis now a non-nullOperationTypeNode. It is now safe to compare this value without having to check forundefined. -
#12398
8cf5077Thanks @jerelmiller! - Apollo Client no longer wraps errors inApolloError.ApolloErrorhas been replaced with separate error classes depending on the cause of the error. As such, APIs that return anerrorproperty have been updated to use the genericErrortype. Useinstanceofto check for more specific error types.Migration guide
ApolloErrorencapsulated 4 main error properties. The type of error would determine which property was set:graphqlErrors- Errors returned from theerrorsfield by the GraphQL servernetworkError- Any non-GraphQL error that caused the query to failprotocolErrors- Transport-level errors that occur during multipart HTTP subscriptionsclientErrors- A space to define custom errors. Mostly unused.
These errors were mutally exclusive, meaning both
networkErrorandgraphqlErrorswere never set simultaneously. The following replaces each of these fields fromApolloError.graphqlErrorsGraphQL errors are now encapsulated in a
CombinedGraphQLErrorsinstance. You can access the raw GraphQL errors via theerrorsproperty.import { CombinedGraphQLErrors } from "@apollo/client"; // ... const { error } = useQuery(query); if (error && error instanceof CombinedGraphQLErrors) { console.log(error.errors); }networkErrorNetwork errors are no longer wrapped and are instead passed through directly.
const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { observer.error(new Error("Test error")); }); }), }); // ... const { error } = useQuery(query); // error is `new Error('Test error')`;protocolErrorsProtocol errors are now encapsulated in a
CombinedProtocolErrorsinstance. You can access the raw protocol errors via theerrorsproperty.import { CombinedProtocolErrors } from "@apollo/client"; // ... const { error } = useSubscription(subscription); if (error && error instanceof CombinedProtocolErrors) { console.log(error.errors); }clientErrorsThese were unused by the client and have no replacement. Any non-GraphQL or non-protocol errors are now passed through unwrapped.
Strings as errors
If the link sends a string error, Apollo Client will wrap this in an
Errorinstance. This ensureserrorproperties are guaranteed to be of typeError.const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { // Oops we sent a string instead of wrapping it in an `Error` observer.error("Test error"); }); }), }); // ... const { error } = useQuery(query); // The error string is wrapped and returned as `new Error('Test error')`;Non-error types
If the link chain sends any other object type as an error, Apollo Client will wrap this in an
UnknownErrorinstance with thecauseset to the original object. This ensureserrorproperties are guaranteed to be of typeError.const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { observer.error({ message: "Not a proper error type" }); }); }), }); // ... const { error } = useQuery(query); // error is an `UnknownError` instance. error.cause returns the original object. -
#12809
e2a0be8Thanks @jerelmiller! -operation.operationNameis now set asstring | undefinedwhereundefinedrepresents an anonymous query. PreviouslyoperationNamewould return an empty string as theoperationNamefor anonymous queries. -
#12450
876d070Thanks @jerelmiller! - Remove theTCacheShapegeneric argument toApolloClient.client.extract()now returnsunknownby default. You will either need to type-cast this to the expected serialized shape, or use thecache.extract()directly from the subclass to get more specific types. -
#12774
511b4f3Thanks @jerelmiller! - Apply document transforms before reading data from the cache forclient.readQuery,client.readFragment,client.watchFragment,useFragment, anduseSuspenseFragment.NOTE: This change does not affect the equivalent
cache.*APIs. To read data from the cache without first running document transforms, runcache.readQuery,cache.readFragment, etc. -
#12705
a60f411Thanks @jerelmiller! -cache-onlyqueries will now initialize withloading: falseandnetworkStatus: NetworkStatus.readywhen there is no data in the cache.This means
useQuerywill no longer render a short initial loading state before renderingloading: falseandObservableQuery.getCurrentResult()will now returnloading: falseimmediately. -
#12475
3de63ebThanks @jerelmiller! - Unify error behavior on mutations for GraphQL errors and network errors by ensuring network errors are subject to theerrorPolicy. Network errors created when using anerrorPolicyofallwill now resolve the promise and be returned on theerrorproperty of the result, or stripped away when theerrorPolicyisnone. -
#12384
6aa6fd3Thanks @jerelmiller! - RemovefromErrorutility function. UsethrowErrorinstead. -
#12649
0be92adThanks @jerelmiller! - TheTDatageneric provided to types that return adataStateproperty is now modified by the givenDataStategeneric instead of passing a modifiedTDatatype. For example, aQueryRefthat could return partial data was defined asQueryRef<DeepPartial<TData>, TVariables>. NowTDatashould be provided unmodified and a set of allowed states should be given instead:QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'>.To migrate, use the following guide to replace your type with the right set of states (all types listed below are changed the same way):
- QueryRef<TData, TVariables> // `QueryRef`'s default is 'complete' | 'streaming' so this can also be left alone if you prefer // All other types affected by this change default to all states + QueryRef<TData, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming'> - QueryRef<TData | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'empty'> - QueryRef<DeepPartial<TData>, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'> - QueryRef<DeepPartial<TData> | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial' | 'empty'>The following types are affected. Provide the allowed
dataStatevalues to theTDataStategeneric:ApolloQueryResultQueryRefPreloadedQueryRefuseLazyQuery.ResultuseQuery.ResultuseReadQuery.ResultuseSuspenseQuery.Result
All
*QueryReftypes default tocomplete | streamingstates while the rest of the types default to'complete' | 'streaming' | 'partial' | 'empty'states. You shouldn't need to provide the states unless you need to either allow for partial data/empty values (*QueryRef) or a restricted set of states. -
#12850
268cd80Thanks @phryneas! - Introduce a versioning policy. -
#12809
e2a0be8Thanks @jerelmiller! - Theconcat,from, andsplitfunctions onApollLinkno longer support a plain request handler function. Please wrap the request handler withnew ApolloLink.const link = new ApolloLink(/* ... */); link.concat( - (operation, forward) => forward(operation), + new ApolloLink((operation, forward) => forward(operation)), ); -
#12802
e2b51b3Thanks @jerelmiller! - Disallow themutationoption for themutatefunction returned fromuseMutation. -
#12211
c2736dbThanks @jerelmiller! - Remove the deprecatedgraphql,withQuery,withMutation,withSubscription, andwithApollohoc components. Use the provided React hooks instead. -
#12690
5812759Thanks @phryneas! - Aliasing a field to an alias beginning with__ac_is now forbidden - this namespace is now reserved for internal use. -
#12559
49ace0eThanks @jerelmiller! - When passing avariableskey with the valueundefined, the value will be replaced by the default value in the query, if it is provided, rather than leave it asundefined.// given this query const query = gql` query PaginatedQuery($limit: Int! = 10, $offset: Int) { list(limit: $limit, offset: $offset) { id } } `; const observable = client.query({ query, variables: { limit: 5, offset: 0 }, }); console.log(observable.variables); // => { limit: 5, offset: 0 } observable.reobserve({ variables: { limit: undefined, offset: 10 } }); // limit is now `10`. This would previously be `undefined` console.log(observable.variables); // => { limit: 10, offset: 10 } -
#12262
10ef733Thanks @jerelmiller! - RemoveitAsynctest utility. -
#12673
cee90abThanks @phryneas! - Adds enhanced client awareness to the client.HttpLinkandBatchHttpLinkwill now per default send information about the client library you are using inextensions.This could look like this:
{ "query": "query GetUser($id: ID!) { user(id: $id) { __typename id name } }", "variables": { "id": 5 }, "extensions": { "clientLibrary": { "name": "@apollo/client", "version": "4.0.0" } } }This feature can be disabled by passing
enhancedClientAwareness: { transport: false }to yourApolloClient,HttpLinkorBatchHttpLinkconstructor options. -
#12742
575bf3eThanks @jerelmiller! - The newSetContextLinkflips theprevContextandoperationarguments in the callback. ThesetContextfunction has remained unchanged.- new SetContextLink((operation, prevContext) => { + new SetContextLink((prevContext, operation) => { // ... }) -
#12536
e14205aThanks @jerelmiller! - An initial loading state is now emitted fromObservableQuerywhen subscribing ifnotifyOnNetworkStatusChangeis set totrue. -
#12465
a132163Thanks @jerelmiller! - Flatten out React hook types. As a result, the base types have been removed. Prefer using the hook types instead. Removed types include:BaseMutationOptionsBaseQueryOptionsBaseSubscriptionOptionsObservableQueryFieldsMutationSharedOptionsQueryFunctionOptions
-
#12675
8f1d974Thanks @phryneas! -ObservableQueryno longer has aqueryIdproperty.ApolloClient.getObservableQueriesno longer returns aMap<string, ObservableQuery>, but aSet<ObservableQuery>. -
#12398
8cf5077Thanks @jerelmiller! - Updates theServerErrorandServerParseErrortypes to be properErrorsubclasses. Perviously these were plainErrorintances with additional properties added at runtime. All properties are retained, butinstanceofchecks now work correctly.import { ServerError, ServerParseError } from "@apollo/client"; if (error instanceof ServerError) { // ... } if (error instanceof ServerParseError) { // ... } -
#12712
bbb2b61Thanks @jerelmiller! -cache-onlyqueries are now excluded fromclient.refetchQueriesin all situations.cache-onlyqueries affected byupdateCacheare also excluded fromrefetchQuerieswhenonQueryUpdatedis not provided. -
#12463
3868df8Thanks @jerelmiller! -useQueryno longer returnsreobserveas part of its result. It was possible to usereobserveto set new options on the underlyingObservableQueryinstance which differed from the options passed to the hook. This could result in unexpected results. Instead prefer to rerender the hook with new options. -
#12367
e6af35eThanks @jerelmiller! -useLazyQueryno longer supports SSR environments and will now throw if theexecutefunction is called in SSR. If you need to run a query in an SSR environment, useuseQueryinstead. -
#12614
d2851e2Thanks @jerelmiller! - Removes theresolversoption fromApolloClient. Local resolvers have instead been moved to the newLocalStateinstance which is assigned to thelocalStateoption inApolloClient. To migrate, move theresolversvalues into aLocalStateinstance and assign that instance tolocalState.new ApolloClient({ - resolvers: { /* ... */ } + localState: new LocalState({ + resolvers: { /* ... */ } + }), }); -
#12475
3de63ebThanks @jerelmiller! -client.mutatenow returns aMutateResultinstead ofFetchResult. As a result, theerrorsproperty has been removed in favor oferrorwhich is set if either a network error occured or GraphQL errors are returned from the server.useMutationnow also returns aMutateResultinstead of aFetchResult. -
#12367
e6af35eThanks @jerelmiller! - The execute function returned fromuseLazyQuerynow only supports thecontextandvariablesoptions. This means that passing options supported by the hook no longer override the hook value.To change options, rerender the component with new options. These options will take effect with the next query execution.
-
#12384
6aa6fd3Thanks @jerelmiller! -ObservableQuerywill no longer terminate on errors and will instead emit anextvalue with anerrorproperty. This ensures thatObservableQueryinstances can continue to receive updates after errors are returned in requests without the need to resubscribe to the observable. -
#12681
b181f98Thanks @jerelmiller! - Changing most options when rerenderinguseQuerywill no longer trigger areobservewhich may cause network fetches. Instead, the changed options will be applied to the next cache update or fetch.Options that now trigger a
reobservewhen changed between renders are:queryvariablesskip- Changing
fetchPolicyto or fromstandby
-
#12787
8ce31faThanks @phryneas! - Generic arguments forCache.ReadOptionswere flipped fromTVariables, TDatatoTData, TVariables. -
#12837
7c49fdcThanks @jerelmiller! - You must now opt in to use GraphQL Codegen data masking types when using Apollo Client's data masking feature. By default, Apollo Client now uses an identity type to apply to masked/unmasked types.If you're using GraphQL Codegen to generate masked types, opt into the GraphQL Codegen masked types using declaration merging on the
TypeOveridesinterface.import { GraphQLCodegenDataMasking } from "@apollo/client/masking"; declare module "@apollo/client" { export interface TypeOverrides extends GraphQLCodegenDataMasking.TypeOverrides {} } -
#12824
0506f12Thanks @jerelmiller! - Ensure theerrorargument for thedelayandattemptsfunctions onRetryLinkare anErrorLike. -
#12398
8cf5077Thanks @jerelmiller! - Removes thethrowServerErrorutility function. Now thatServerErroris anErrorsubclass, you can throw these errors directly:import { ServerError } from "@apollo/client"; // instead of throwServerError(response, result, "error message"); // Use throw new ServerError("error message", { response, result }); -
#12837
7c49fdcThanks @jerelmiller! - The types mode for data masking has been removed. Adding a types mode to theDataMaskinginterface has no effect. Remove themodekey in the module where you declare theDataMaskingtype for the@apollo/clientmodule.As a result, the
MaskedandMaskedDocumentNodetypes have also been removed since these have no effect when types are preserved. -
#12304
86469a2Thanks @jerelmiller! - TheCache.DiffResult<T>type is now a union type with better type safety for both complete and partial results. Checkingdiff.completewill now narrow the type ofresultdepending on whether the value istrueorfalse.When
true,diff.resultwill be a non-null value equal to theTgeneric type. Whenfalse,diff.resultnow reportsresultasDeepPartial<T> | nullindicating that fields in the result may be missing (DeepPartial<T>) or empty entirely (null). -
#12731
0198870Thanks @phryneas! - Ship React Compiler compiled React hooks in@apollo/client/react/compiled.We now ship a React-Compiler compiled version of the React hooks in
@apollo/client/react/compiled.This entry point contains everything that
@apollo/client/reactdoes, so you can use it as a drop-in replacement in your whole application if you choose to use the compiled hooks. -
#12446
ab920d2Thanks @jerelmiller! - Removes thedefaultOptionsoption fromuseQuery. Use options directly or use the globalApolloClientdefaultOptions. -
#12649
0be92adThanks @jerelmiller! - Remove the deprecatedQueryReferencetype. Please useQueryRefinstead. -
#12396
00f3d0aThanks @jerelmiller! - Remove theerrorsproperty from the results emitted fromObservableQueryor returned fromclient.query. Read errors from theerrorproperty instead. -
#12367
e6af35eThanks @jerelmiller! - The result resolved from the promise returned from the execute function inuseLazyQueryis now anApolloQueryResulttype and no longer includes all the fields returned from theuseLazyQueryhook tuple.If you need access to the additional properties such as
called,refetch, etc. not included inApolloQueryResult, read them from the hook instead. -
#12531
7784b46Thanks @jerelmiller! - Mocked responses passed toMockLinknow accept a callback for therequest.variablesoption. This is used to determine if the mock should be matched for a set of request variables. With this change, thevariableMatcheroption has been removed in favor of passing a callback tovariables. Update by moving the callback function fromvariableMatchertorequest.variables.new MockLink([ { request: { query, + variables: (requestVariables) => true }, - variableMatcher: (requestVariables) => true } ]); -
#12793
24e98a1Thanks @phryneas! -ApolloConsumerhas been removed - please useuseApolloClientinstead. -
#12714
0e39469Thanks @phryneas! - Rework option handling forfetchMore.- Previously, if the
queryoption was specified, no options would be inherited from the underlyingObservableQuery. Now, even ifqueryis specified, all unspecified options except forvariableswill be inherited from the underlyingObservableQuery. - If
queryis not specified,variableswill still be shallowly merged with thevariablesof the underlyingObservableQuery. If aqueryoption is specified, thevariablespassed tofetchMoreare used instead. errorPolicyoffetchMorewill now always default to"none"instead of inherited from theObservableQueryoptions. This can prevent accidental cache writes of partial data for a paginated query. To opt into receive partial data that may be written to the cache, pass anerrorPolicytofetchMoreto override the default.
- Previously, if the
-
#12614
d2851e2Thanks @jerelmiller! - Remove local resolvers APIs fromApolloClientin favor oflocalState. Methods removed are:addResolversgetResolverssetResolverssetLocalStateFragmentMatcher
-
#12576
a92ff78Thanks @jerelmiller! -ApolloLink.executenow requires a third argument which provides theclientthat initiated the request to the link chain. If you useexecutedirectly, add a third argument with aclientproperty:ApolloLink.execute(link, operation, { client }); // or if you import the `execute` function directly: execute(link, operation, { client }); -
#12526
391af1dThanks @phryneas! - The@apollo/clientand@apollo/client/coreentry points are now equal. In the next major, the@apollo/client/coreentry point will be removed. Please change imports over from@apollo/client/coreto@apollo/client. -
#12700
8e96e08Thanks @phryneas! - Added a newStreamingtype that will markdatain results whiledataStateis"streaming".Streaming<TData>defaults toTData, but can be overwritten in userland to integrate with different codegen dialects.You can override this type globally - this example shows how to override it with
DeepPartial<TData>:import { HKT, DeepPartial } from "@apollo/client/utilities"; type StreamingOverride<TData> = DeepPartial<TData>; interface StreamingOverrideHKT extends HKT { return: StreamingOverride<this["arg1"]>; } declare module "@apollo/client" { export interface TypeOverrides { Streaming: StreamingOverrideHKT; } } -
#12367
e6af35eThanks @jerelmiller! -useLazyQuerywill no longer rerender with the loading state when calling the execute function the first time unless thenotifyOnNetworkStatusChangeoption is set totrue(which is the new default).If you prefer the behavior from 3.x, rerender the component with
notifyOnNetworkStatusChangeset tofalseafter the execute function is called the first time.function MyComponent() { const [notifyOnNetworkStatusChange, setNotifyOnNetworkStatusChange] = useState(true); const [execute] = useLazyQuery(query, { notifyOnNetworkStatusChange }); async function runExecute() { await execute(); // Set to false after the initial fetch to stop receiving notifications // about changes to the loading states. setNotifyOnNetworkStatusChange(false); } // ... } -
#12475
3de63ebThanks @jerelmiller! - Mutations no longer report errors if the GraphQL result from the server contains an empty array of errors. -
#12254
0028ac0Thanks @jerelmiller! - Changes the defaultAcceptheader toapplication/graphql-response+json. -
#12633
9bfb51fThanks @phryneas! - If theexecutefunction ofuseLazyQueryis executed, previously started queries from the sameuseLazyQueryusage will be rejected with anAbortErrorunless.retain()is called on the promise returned by previousexecutecalls.Please keep in mind that
useLazyQueryis primarily meant as a means to synchronize your component to the status of a query and that it's purpose it not to make a series of network calls. If you plan on making a series of network calls without the need to synchronize the result with your component, consider usingApolloClient.queryinstead. -
#12513
9c3207cThanks @phryneas! - Removed the@apollo/client/react/parserentry point. There is no replacement. -
#12430
2ff66d0Thanks @jerelmiller! -ObservableQuery.setVariableswill now resolve with the last emitted result instead ofundefinedwhen either the variables match the current variables or there are no subscribers to the query. -
#12685
3b74800Thanks @jerelmiller! - Remove the check and warning forcache.fragmentMatcheswhen applying data masking.cache.fragmentMatchesis a required API and data masking may crash whencache.fragmentMatchesdoes not exist. -
#12385
cad5117Thanks @phryneas! - Apollo Client now defaults to production mode, not development mode, if the environment cannot be determined.In modern bundlers, this should automatically be handled by the bundler loading the bundler with the
developmentexport condition.If neither the
productionnor thedevelopmentexport condition are used by the bundler/runtime, Apollo Client will fall back toglobalThis.__DEV__to determine if it should run in production or development mode.Unlike Apollo Client 3 though, if
globalThis.__DEV__is not set totrue, Apollo Client will now default toproduction, not todevelopment, behaviour.This switch to explicilty requiring
truealso resolves a situation where an HTML element withid="__DEV__"would create a global__DEV__variable with a referent to the DOM element, which in the past was picked up as "truthy" and would have triggered development mode. -
#12644
fe2f005Thanks @jerelmiller! - Change the defaultAcceptheader toapplication/graphql-response+json,application/json;q=0.9. -
#12476
6afff60Thanks @jerelmiller! - Unify error behavior on subscriptions for GraphQL errors and network errors by ensuring network errors are subject to theerrorPolicy. Network errors that terminate the connection will now be emitted on theerrorproperty passed to thenextcallback followed by a call to thecompletecallback. -
#12499
ce35ea2Thanks @phryneas! - Enable React compiler for hooks in ESM builds. -
#12367
e6af35eThanks @jerelmiller! - Thereobserveoption is no longer available in the result returned fromuseLazyQuery. This was considered an internal API and should not be used directly. -
#12333
3e4beaaThanks @jerelmiller! - Fix type ofdataproperty onApolloQueryResult. Previously this field was non-optional, non-nullTData, however at runtime this value could be set toundefined. This field is now reported asTData | undefined.This will affect you in a handful of places:
- The
dataproperty emitted from the result passed to thenextcallback fromclient.watchQuery - Fetch-based APIs that return an
ApolloQueryResulttype such asobservableQuery.refetch,observableQuery.fetchMore, etc.
- The
-
#12644
fe2f005Thanks @jerelmiller! -HttpLinkandBatchHttpLinkno longer emit anextnotification with the JSON-parsed response body when a well-formed GraphQL response is returned and aServerErroris thrown. -
#12742
575bf3eThanks @jerelmiller! - Theoperationargument to the callback passed toSetContextLinkis now of typeSetContextLink.SetContextOperationwhich is anOperationwithout thegetContextorsetContextfunctions. Previously the type ofoperationwasGraphQLRequestwhich had access to acontextproperty. Thecontextproperty was alwaysundefinedand could result in bugs when using it instead of theprevContextargument.This change means the
operationargument now contains an accessibleclientproperty. -
#12639
1bdf489Thanks @jerelmiller! - Move internal testing utilities in@apollo/client/testingto@apollo/client/testing/internaland remove deprecated testing utilities. Some of the testing utilities exported from the@apollo/client/testingendpoint were not considered stable. As a result of this change, testing utilities or types exported from@apollo/client/testingare now considered stable and will not undergo breaking changes.The following APIs were removed. To migrate, update usages of the following APIs as such:
createMockClient- const client = createMockClient(data, query, variables); + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]), + });mockObservableLink- const link = mockObservableLink(); + const link = new MockSubscriptionLink();mockSingleLink- const link = mockSingleLink({ - request: { query, variables }, - result: { data }, - }); + const link = new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]); -
#12614
d2851e2Thanks @jerelmiller! - Third-party caches must now implement thefragmentMatchesAPI. AdditionallyfragmentMatchesmust be able to handle bothInlineFragmentNodeandFragmentDefinitionNodenodes.class MyCache extends ApolloCache { // This is now required public fragmentMatches( fragment: InlineFragmentNode | FragmentDefinitionNode, typename: string ): boolean { return; // ... logic to determine if typename matches fragment } } -
#12367
e6af35eThanks @jerelmiller! - The promise returned when calling the execute function fromuseLazyQuerywill now reject when using anerrorPolicyofnonewhen GraphQL errors are returned from the result. -
#12684
e697431Thanks @jerelmiller! - RemovecontextfromuseLazyQueryhook options. If used,contextmust now be provided to theexecutefunction.contextwill reset to{}if not provided as an option toexecute. -
#12704
45dba43Thanks @jerelmiller! - TheErrorResponseobject passed to thedisableandretrycallback options provided tocreatePersistedQueryLinkno longer provides separategraphQLErrorsandnetworkErrorproperties and instead have been combined to a singleerrorproperty of typeErrorLike.// The following also applies to the `retry` function since it has the same signature createPersistedQueryLink({ - disable: ({ graphQLErrors, networkError }) => { + disable: ({ error }) => { - if (graphQLErrors) { + if (CombinedGraphQLErrors.is(error)) { // ... handle GraphQL errors } - if (networkError) { + if (error) { // ... handle link errors } // optionally check for a specific kind of error - if (networkError) { + if (ServerError.is(error)) { // ... handle a server error } });The
responseproperty has also been renamed toresult.createPersistedQueryLink({ - disable: ({ response }) => { + disable: ({ result }) => { // ... handle GraphQL errors } } }); -
#12823
19e315eThanks @jerelmiller! - Move all 1st party link types into a namespace. -
#12223
69c1cb6Thanks @jerelmiller! - RemovesubscribeAndCounttesting utility from@apollo/client/testing. -
#12300
4d581e4Thanks @jerelmiller! - Moves all React-related exports to the@apollo/client/reactentrypoint and out of the main@apollo/cliententrypoint. This prevents the need to install React in order to use the core client.The following is a list of exports available in
@apollo/clientthat should now import from@apollo/client/react.ApolloConsumerApolloProvidercreateQueryPreloadergetApolloContextskipTokenuseApolloClientuseBackgroundQueryuseFragmentuseLazyQueryuseLoadableQueryuseMutationuseQueryuseQueryRefHandlersuseReactiveVaruseReadQueryuseSubscriptionuseSuspenseQuery
The following is a list of exports available in
@apollo/client/testingthat should now import from@apollo/client/testing/react:MockedProvider
-
#12525
8785186Thanks @jerelmiller! - Throw an error when a client-only query is used in a mocked response passed toMockLink. -
#12588
eed825aThanks @jerelmiller! - RemoveTContextgeneric argument from all types that use it.TContextis replaced withDefaultContextwhich can be modified using declaration merging. -
#12647 [
a70fac6](https://github.com/apollographql/apollo-client/commit/a70fac6
Note truncated.
Additional notes2 sources agree
Open source →Apollo Client 4.0 Release Notes
Apollo Client 4.0 delivers a more modern, efficient, and type-safe GraphQL client experience through various architectural improvements and API refinements. This release focuses on developer experience, bundle size optimization, and framework flexibility.
- Opt-in Local State Management: The
- 4.0.0-rc.1320 Aug 2025pre-release
Release notes3 sources agree
Open source → - 4.0.0-rc.1215 Aug 2025pre-release
- 4.0.0-rc.1114 Aug 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12840
83e132aThanks @phryneas! - If you use an incremental delivery handler, you now have to explicitly opt into adding the chunk types to theApolloLink.Resulttype.import { Defer20220824Handler } from "@apollo/client/incremental"; declare module "@apollo/client" { export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {} } -
#12841
65b503fThanks @jerelmiller! - Remove theDataMaskinginterface exported from@apollo/clientand@apollo/client/masking.
-
- 4.0.0-rc.1013 Aug 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12837
7c49fdcThanks @jerelmiller! - You must now opt in to use GraphQL Codegen data masking types when using Apollo Client's data masking feature. By default, Apollo Client now uses an identity type to apply to masked/unmasked types.If you're using GraphQL Codegen to generate masked types, opt into the GraphQL Codegen masked types using declaration merging on the
TypeOveridesinterface.import { GraphQLCodegenDataMasking } from "@apollo/client/masking"; declare module "@apollo/client" { export interface TypeOverrides extends GraphQLCodegenDataMasking.TypeOverrides {} } -
#12837
7c49fdcThanks @jerelmiller! - The types mode for data masking has been removed. Adding a types mode to theDataMaskinginterface has no effect. Remove themodekey in the module where you declare theDataMaskingtype for the@apollo/clientmodule.As a result, the
MaskedandMaskedDocumentNodetypes have also been removed since these have no effect when types are preserved.
-
- 4.0.0-rc.97 Aug 2025pre-release
Release notes3 sources agree
Open source →Minor Changes
- #12828
81b03d8Thanks @phryneas! -invariant.errorwill now also log in production builds, not only dev builds
Patch Changes
- #12828
- 4.0.0-rc.85 Aug 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12825
292b949Thanks @jerelmiller! - TheserializeFetchParameterhelper is no longer exported andJSON.stringifyis used directly. As such, theClientParseErrortype has also been removed in favor of throwing any JSON serialize errors directly. -
#12824
0506f12Thanks @jerelmiller! - Ensure theerrorargument for thedelayandattemptsfunctions onRetryLinkare anErrorLike. -
#12823
19e315eThanks @jerelmiller! - Move all 1st party link types into a namespace. -
#12823
19e315eThanks @jerelmiller! - TheOperationBatcherclass is no longer exported from@apollo/client/link/batch. It is an implementation detail ofBatchLinkand should not be relied on directly.
Patch Changes
-
#12824
0506f12Thanks @jerelmiller! -RetryLinknow emits anextevent instead of anerrorevent when encountering a protocol errors for multipart subscriptions when the operation is not retried. This ensures the observable notification remains the same as whenRetryLinkis not used. -
#12819
7ff548dThanks @jerelmiller! - update type ofHttpLink.Options.fetchOptionstoRequestInit -
#12820
fba3d9eThanks @jerelmiller! - ThefetchOptionsoption provided toHttpLinkandBatchHttpLinkis nowRequestInitinstead ofany. Thecredentialsoption is now aRequestCredentialstype instead of astring. -
#12823
19e315eThanks @jerelmiller! - Fix the type of the argument for thesha256function forPersistedQueryLinkfrom...any[]tostring. -
#12821
223a409Thanks @jerelmiller! - Add a deprecation warning toWebSocketLink.
-
- 4.0.0-rc.71 Aug 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12809
e2a0be8Thanks @jerelmiller! -operation.getContextnow returns aReadonly<OperationContext>type. -
#12809
e2a0be8Thanks @jerelmiller! - TheApolloLink.Request(i.e.GraphQLRequest) passed toApolloLink.executeno longer acceptsoperationNameandoperationTypeoptions. These properties are derived from thequeryand set on the returnedApolloLink.Operationtype. -
#12808
8e31a23Thanks @phryneas! - HTTP Multipart handling will now throw an error if the connection closed before the final boundary has been received. Data after the final boundary will be ignored. -
#12809
e2a0be8Thanks @jerelmiller! -operation.operationTypeis now a non-nullOperationTypeNode. It is now safe to compare this value without having to check forundefined. -
#12809
e2a0be8Thanks @jerelmiller! -operation.operationNameis now set asstring | undefinedwhereundefinedrepresents an anonymous query. PreviouslyoperationNamewould return an empty string as theoperationNamefor anonymous queries. -
#12809
e2a0be8Thanks @jerelmiller! - Theconcat,from, andsplitfunctions onApollLinkno longer support a plain request handler function. Please wrap the request handler withnew ApolloLink.const link = new ApolloLink(/* ... */); link.concat( - (operation, forward) => forward(operation), + new ApolloLink((operation, forward) => forward(operation)), ); -
#12809
e2a0be8Thanks @jerelmiller! -transformOperationandvalidateOperationhave been removed and are no longer exported from@apollo/client/link/utils. These utilities have been merged into the implementation ofcreateOperation. As a result,createOperationnow returns a well-formedOperationobject. PreviouslycreateOperationrelied on an external call totransformOperationto provide a well-formedOperationtype. If you usecreateOperationdirectly, remove the calls totransformOperationandvalidateOperationand pass the request directly. -
#12809
e2a0be8Thanks @jerelmiller! - The request handler provided toApolloLinkmust now return anObservable.nullis no longer supported as a valid return value. If you rely onnullso thatApolloLinkprovides an empty observable, use theEMPTYobservable from RxJS instead:import { ApolloLink } from "@apollo/client"; + import { EMPTY } from "rxjs"; const link = new ApolloLink((operation, forward) => { - return null; + return EMPTY; });If you have a custom link that overrides the
requestmethod, removenullfrom the return signature:class MyCustomLink extends ApolloLink { request( operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction, - ): Observable<ApolloLink.Result> | null { + ): Observable<ApolloLink.Result> { // implementation } } -
#12809
e2a0be8Thanks @jerelmiller! -createOperationno longer acceptscontextas the first argument. Instead make surecontextis set as thecontextproperty on the request passed tocreateOperation.createOperation( - startingContext, - { query }, + { query, context: startingContext }, { client } ); -
#12809
e2a0be8Thanks @jerelmiller! - Remove theTVariablesgeneric argument on theGraphQLRequesttype. -
#12809
e2a0be8Thanks @jerelmiller! - The context object returned fromoperation.getContext()is now frozen to prevent mutable changes to the object which could result in subtle bugs. This applies to thepreviousContextobject passed to theoperation.setContext()callback as well. -
#12809
e2a0be8Thanks @jerelmiller! - Theforwardfunction passed to the request handler is now always provided torequestand no longer optional. If you create custom links by subclassingApolloLink, theforwardfunction no longer needs to be optional:class CustomLink extends ApolloLink { request( operation: ApolloLink.Operation, // This no longer needs to be typed as optional forward: ApolloLink.ForwardFunction ) { // ... } }As a result of this change,
ApolloLinkno longer detects terminating links by checking function arity on the request handler. This means using methods such asconcaton a terminating link no longer emit a warning. On the flip side, if the terminating link calls theforwardfunction, a warning is emitted and an observable that immediately completes is returned which will result in an error from Apollo Client.
Minor Changes
-
#12809
e2a0be8Thanks @jerelmiller! -ApolloLink'sconcatmethod now accepts multiple links to concatenate together.const first = new ApolloLink(); const link = first.concat(second, third, fouth); -
#12809
e2a0be8Thanks @jerelmiller! - Many of the types exported from@apollo/client/linknow live on theApolloLinknamespace. The old types are now deprecated in favor of the namespaced types.FetchResult->ApolloLink.ResultGraphQLRequest->ApolloLink.RequestNextLink->ApolloLink.ForwardFunctionOperation->ApolloLink.OperationRequestHandler->ApolloLink.RequestHandler
-
#12809
e2a0be8Thanks @jerelmiller! - The staticApolloLink.concatmethod is now deprecated in favor ofApolloLink.from.ApolloLink.concatis now an alias forApolloLink.fromso preferApolloLink.frominstead.
Patch Changes
-
#12809
e2a0be8Thanks @jerelmiller! - The individualempty,concat,fromandsplitfunctions exported from@apollo/client/linkare now deprecated in favor of using the static functions instead.import { ApolloLink, - concat, - empty, - from, - split, } from "@apollo/client/link"; - concat(first, second); + ApolloLink.concat(first, second); - empty(); + ApolloLink.empty(); - from([first, second]); + ApolloLink.from([first, second]); - split( + ApolloLink.split( (operation) => /* */, first, second );
-
- 4.0.0-rc.628 Jul 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12787
8ce31faThanks @phryneas! - RemoveDataProxynamespace and interface. -
#12788
4179446Thanks @phryneas! -TVariablesnow alwaysextends OperationVariablesin all interfaces. -
#12802
e2b51b3Thanks @jerelmiller! - Disallow themutationoption for themutatefunction returned fromuseMutation. -
#12787
8ce31faThanks @phryneas! - Generic arguments forCache.ReadOptionswere flipped fromTVariables, TDatatoTData, TVariables. -
#12793
24e98a1Thanks @phryneas! -ApolloConsumerhas been removed - please useuseApolloClientinstead.
Patch Changes
- #12782
742b3a0Thanks @jerelmiller! - MoveApolloClient,ObservableQuery, andApolloCache.watchFragmentmethod options and result types into namespaces. The old types are now exported as deprecated.
-
- 4.0.0-rc.518 Jul 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12776
bce9b74Thanks @jerelmiller! - Report masked fragments as complete even when a nested masked fragment contains partial data. -
#12774
511b4f3Thanks @jerelmiller! - Apply document transforms before reading data from the cache forclient.readQuery,client.readFragment,client.watchFragment,useFragment, anduseSuspenseFragment.NOTE: This change does not affect the equivalent
cache.*APIs. To read data from the cache without first running document transforms, runcache.readQuery,cache.readFragment, etc.
Minor Changes
- #12776
bce9b74Thanks @jerelmiller! - AdddataStateto the value emitted fromclient.watchFragment.
Patch Changes
-
#12776
bce9b74Thanks @jerelmiller! -cache.watchFragmentnow returns anUnmasked<TData>result sincecache.watchFragmentdoes not mask fragment spreads. -
#12370
0517163Thanks @phryneas! -InMemoryCache: Fields with an empty argument object are now saved the same way as fields without arguments.Previously, it was possible that the reponses for these two queries would be stored differently in the cache:
query PlainAccess { myField }would be stored as
myFieldandquery AccessWithoutOptionalArgument($optional: String) { myField(optional: $optional) }would be stored as
myField({"optional":"Foo"})if called with{optional: "Foo"}and asmyField({})if called without the optional argument.The cases
myFieldandmyField({})are equivalent from the perspective of a GraphQL server, and so in the future both of these will be stored asmyFieldin the cache. -
#12775
454ec78Thanks @jerelmiller! - Don't exportgqlfrom@apollo/client/reactentrypoint. Import from@apollo/clientinstead. -
#12761
db6f7c3Thanks @phryneas! - Deprecate second argument toreadFragmentandreadQuery-optimisticshould be passed as part of the object in the first argument instead.
-
- 4.0.0-rc.48 Jul 2025pre-release
- 4.0.0-rc.31 Jul 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12731
0198870Thanks @phryneas! - Ship React Compiler compiled React hooks in@apollo/client/react/compiled.We now ship a React-Compiler compiled version of the React hooks in
@apollo/client/react/compiled.This entry point contains everything that
@apollo/client/reactdoes, so you can use it as a drop-in replacement in your whole application if you choose to use the compiled hooks.
Minor Changes
- #12753
b85818dThanks @jerelmiller! - Renamedclient.reFetchObservableQueriestoclient.refetchObservableQueries.client.reFetchObservableQueriesis still available as an alias, but is now deprecated and will be removed in a future major version.
-
- 4.0.0-rc.227 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12742
575bf3eThanks @jerelmiller! - The newSetContextLinkflips theprevContextandoperationarguments in the callback. ThesetContextfunction has remained unchanged.- new SetContextLink((operation, prevContext) => { + new SetContextLink((prevContext, operation) => { // ... }) -
#12742
575bf3eThanks @jerelmiller! - Theoperationargument to the callback passed toSetContextLinkis now of typeSetContextLink.SetContextOperationwhich is anOperationwithout thegetContextorsetContextfunctions. Previously the type ofoperationwasGraphQLRequestwhich had access to acontextproperty. Thecontextproperty was alwaysundefinedand could result in bugs when using it instead of theprevContextargument.This change means the
operationargument now contains an accessibleclientproperty.
Minor Changes
-
#12740
1c6e03cThanks @phryneas! - Overridable types fordataState: "complete",dataState: "streaming"anddataState: "partial"responses.This adds the
DataValuenamespace exported from Apollo Client with the three typesDataValue.Complete,DataValue.StreamingandDataValue.Partial.These types will be used to mark
TDatain the respective states.Completedefaults toTDataStreamingdefaults toTDataPartialdefaults toDeepPartial<TData>
All three can be overwritten, e.g. to be
DeepReadonlyusing higher kinded types by following this pattern:import { HKT, DeepPartial } from "@apollo/client/utilities"; import { DeepReadonly } from "some-type-helper-library"; interface CompleteOverride extends HKT { return: DeepReadonly<this["arg1"]>; } interface StreamingOverride extends HKT { return: DeepReadonly<this["arg1"]>; } interface PartialOverride extends HKT { return: DeepReadonly<DeepPartial<this["arg1"]>>; } declare module "@apollo/client" { export interface TypeOverrides { Complete: CompleteOverride; Streaming: StreamingOverride; Partial: PartialOverride; } }
Patch Changes
-
- 4.0.0-rc.124 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12735
5159880Thanks @jerelmiller! - Remove deprecatedresultCacheMaxSizeoption fromInMemoryCacheoptions. -
#12735
5159880Thanks @jerelmiller! - Remove deprecatedconnectToDevtoolsoption fromApolloClientOptions. Usedevtools.enabledinstead.
Minor Changes
-
#12725
89ac725Thanks @jerelmiller! - AddoperationTypetooperationinApolloLink. This means that determining whether aqueryis a specific operation type can now be compared with this property instead of usinggetMainDefinition.- import { getMainDefinition } from "@apollo/client/utilities"; + import { OperationTypeNode } from "graphql"; ApolloLink.split( - ({ query }) => { - const definition = getMainDefinition(query); - return ( - definition.kind === 'OperationDefinition' && - definition.operation === 'subscription' - ); - return - }, + ({ operationType }) => { + return operationType === OperationTypeNode.SUBSCRIPTION; + }, conditionTrueLink, conditionFalseLink, );
Patch Changes
-
#12728
07a0c8cThanks @jerelmiller! - Export theIgnoreModifiertype from@apollo/client/cache. -
#12735
5159880Thanks @jerelmiller! - Change theunsafePreviousDataargument onUpdateQueryMapFnandSubscribeToMoreQueryFnto aDeepPartialsince the result may contain partial data. -
#12734
037979dThanks @jerelmiller! - Don't warn about a missing resolver if a@clientdoes not have a configured resolver. It is possible the cache contains areadfunction for the field and the warning added confusion.Note that
readfunctions without a defined resolver will receive theexistingargument asnullinstead ofundefinedeven when data hasn't been written to the cache. This is becauseLocalStatesets a default value ofnullwhen a resolver is not defined to ensure that the field contains a value in case areadfunction is not defined rather than omitting the field entirely. -
#12725
89ac725Thanks @jerelmiller! - ExportgetMainDefinitionfrom@apollo/client/utilities. -
#12729
699c830Thanks @jerelmiller! - EnsureuseQueryrerenders whennotifyOnNetworkStatusChangeisfalseand arefetchthat changes variables returns a result deeply equal to previous variables.
-
- 4.0.0-rc.018 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
- #12718
ecfc02aThanks @jerelmiller! - Version bump only to release latest asrc.
- #12718
- 4.0.0-alpha.2318 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12712
bbb2b61Thanks @jerelmiller! - An error is now thrown when trying to callfetchMoreon acache-onlyquery. -
#12712
bbb2b61Thanks @jerelmiller! -cache-onlyqueries are no longer refetched when callingclient.reFetchObservableQuerieswhenincludeStandbyistrue. -
#12705
a60f411Thanks @jerelmiller! -cache-onlyqueries will now initialize withloading: falseandnetworkStatus: NetworkStatus.readywhen there is no data in the cache.This means
useQuerywill no longer render a short initial loading state before renderingloading: falseandObservableQuery.getCurrentResult()will now returnloading: falseimmediately. -
#12712
bbb2b61Thanks @jerelmiller! -cache-onlyqueries are now excluded fromclient.refetchQueriesin all situations.cache-onlyqueries affected byupdateCacheare also excluded fromrefetchQuerieswhenonQueryUpdatedis not provided. -
#12681
b181f98Thanks @jerelmiller! - Changing most options when rerenderinguseQuerywill no longer trigger areobservewhich may cause network fetches. Instead, the changed options will be applied to the next cache update or fetch.Options that now trigger a
reobservewhen changed between renders are:queryvariablesskip- Changing
fetchPolicyto or fromstandby
-
#12714
0e39469Thanks @phryneas! - Rework option handling forfetchMore.- Previously, if the
queryoption was specified, no options would be inherited from the underlyingObservableQuery. Now, even ifqueryis specified, all unspecified options except forvariableswill be inherited from the underlyingObservableQuery. - If
queryis not specified,variableswill still be shallowly merged with thevariablesof the underlyingObservableQuery. If aqueryoption is specified, thevariablespassed tofetchMoreare used instead. errorPolicyoffetchMorewill now always default to"none"instead of inherited from theObservableQueryoptions. This can prevent accidental cache writes of partial data for a paginated query. To opt into receive partial data that may be written to the cache, pass anerrorPolicytofetchMoreto override the default.
- Previously, if the
-
#12700
8e96e08Thanks @phryneas! - Added a newStreamingtype that will markdatain results whiledataStatusis"streaming".Streaming<TData>defaults toTData, but can be overwritten in userland to integrate with different codegen dialects.You can override this type globally - this example shows how to override it with
DeepPartial<TData>:import { HKT, DeepPartial } from "@apollo/client/utilities"; type StreamingOverride<TData> = DeepPartial<TData>; interface StreamingOverrideHKT extends HKT { return: StreamingOverride<this["arg1"]>; } declare module "@apollo/client" { export interface TypeOverrides { Streaming: StreamingOverrideHKT; } } -
#12499
ce35ea2Thanks @phryneas! - Enable React compiler for hooks in ESM builds. -
#12704
45dba43Thanks @jerelmiller! - TheErrorResponseobject passed to thedisableandretrycallback options provided tocreatePersistedQueryLinkno longer provides separategraphQLErrorsandnetworkErrorproperties and instead have been combined to a singleerrorproperty of typeErrorLike.// The following also applies to the `retry` function since it has the same signature createPersistedQueryLink({ - disable: ({ graphQLErrors, networkError }) => { + disable: ({ error }) => { - if (graphQLErrors) { + if (CombinedGraphQLErrors.is(error)) { // ... handle GraphQL errors } - if (networkError) { + if (error) { // ... handle link errors } // optionally check for a specific kind of error - if (networkError) { + if (ServerError.is(error)) { // ... handle a server error } });The
responseproperty has also been renamed toresult.createPersistedQueryLink({ - disable: ({ response }) => { + disable: ({ result }) => { // ... handle GraphQL errors } } }); -
#12712
bbb2b61Thanks @jerelmiller! -cache-onlyqueries no longer poll when apollIntervalis set. Instead a warning is now emitted that polling has no effect. If thefetchPolicyis changed tocache-onlyafter polling is already active, polling is stopped. -
#12704
45dba43Thanks @jerelmiller! - Theresponseproperty inonErrorlink has been renamed toresult.- onError(({ response }) => { + onError(({ result }) => { // ... }); -
#12715
0be0b3fThanks @phryneas! - All links are now available as classes. The old creator functions have been deprecated.Please migrate these function calls to class creations:
import { - setContext + SetContextLink } from "@apollo/client/link/context" -const link = setContext(...) +const link = new SetContextLink(...)import { - createHttpLink + HttpLink } from "@apollo/client/link/http" -const link = createHttpLink(...) +const link = new HttpLink(...)import { - createPersistedQueryLink + PersistedQueryLink } from "@apollo/client/link/persisted-queries" -const link = createPersistedQueryLink(...) +const link = new PersistedQueryLink(...)import { - removeTypenameFromVariables + RemoveTypenameFromVariablesLink } from "@apollo/client/link/remove-typename" -const link = removeTypenameFromVariables(...) +const link = new RemoveTypenameFromVariablesLink(...)
Minor Changes
-
#12711
f730f83Thanks @jerelmiller! - Add anextensionsproperty toCombinedGraphQLErrorsto capture any extensions from the original response. -
#12700
8e96e08Thanks @phryneas! - The callback function that can be passed to theApolloClient.mutaterefetchQueriesoption will now receive aFormattedExecutionResultwith an additionaldataStateoption that describes if the result is"streaming"or"complete". This indicates whether thedatavalue is of typeUnmasked<TData>(if"complete")Streaming<Unmasked<TData>>(if"streaming")
-
#12714
0e39469Thanks @phryneas! - Allow passingerrorPolicyoption tofetchMoreand change default value to "none". -
#12714
0e39469Thanks @phryneas! - TheFetchMoreQueryOptionstype has been inlined intoFetchMoreOptions, andFetchMoreQueryOptionshas been removed. -
#12700
8e96e08Thanks @phryneas! - Prioritize usage ofFormattedExecutionResultoverFetchResultwhere applicable.Many APIs used
FetchResultin place ofFormattedExecutionResult, which could cause inconsistencies.FetchResultis now used to refer to an unhandled "raw" result as returned from a link. This can also include incremental results that use a different format.FormattedExecutionResultfrom thegraphqlpackage is now used to represent the execution of a standard GraphQL request without incremental results.
If your custom links access the
dataproperty, you might need to first check if the result is a standard GraphQL result by using theisFormattedExecutionResulthelper from@apollo/client/utilities. -
#12700
8e96e08Thanks @phryneas! - ThemutationResultoption passed to theupdateQueriescallback now has an additional property,dataStatewith possible values of"complete"and"streaming". This indicates whether thedatavalue is of typeUnmasked<TData>(if"complete")Streaming<Unmasked<TData>>(if"streaming")
Patch Changes
-
#12709
9d42e2aThanks @phryneas! - Remove these incremental-format-specific types:ExecutionPatchIncrementalResultExecutionPatchInitialResultExecutionPatchResultIncrementalPayloadPath
-
#12677
94e58edThanks @jerelmiller! - Downgrade minimum supportedrxjspeer dependency version to 7.3.0. -
#12709
9d42e2aThanks @phryneas! - Slightly rework multipart response parsing.This removes last incremental-protocol-specific details from
HttpLinkandBatchHttpLink. -
#12700
8e96e08Thanks @phryneas! - The incremental delivery (@defersupport) implementation is now pluggable.ApolloClientnow per default ships without an incremental format implementation and allows you to swap in the format that you want to use.Usage looks like this:
import { // this is the default NotImplementedHandler, // this implements the `@defer` transport format that ships with Apollo Router Defer20220824Handler, // this implements the `@defer` transport format that ships with GraphQL 17.0.0-alpha.2 GraphQL17Alpha2Handler, } from "@apollo/client/incremental"; const client = new ApolloClient({ cache: new InMemoryCache({ /*...*/ }), link: new HttpLink({ /*...*/ }), incrementalHandler: new Defer20220824Handler(), });We will add handlers for other response formats that can be swapped this way during the lifetime of Apollo Client 4.0.
-
- 4.0.0-alpha.2213 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12673
cee90abThanks @phryneas! - TheincludeExtensionsoption ofHttpLinkandBatchHttpLinknow defaults totrue.If
includeExtensionsistrue, butextensionsis not set or empty, extensions will not be included in outgoing requests. -
#12673
cee90abThanks @phryneas! - TheApolloClientconstructor optionsnameandversionthat are used to configure the client awareness feature have moved onto aclientAwarenesskey.const client = new ApolloClient({ // .. - name: "my-app", - version: "1.0.0", + clientAwareness: { + name: "my-app", + version: "1.0.0", + }, }); -
#12690
5812759Thanks @phryneas! - Aliasing any other field to__typenameis now forbidden. -
#12690
5812759Thanks @phryneas! - Aliasing a field to an alias beginning with__ac_is now forbidden - this namespace is now reserved for internal use. -
#12673
cee90abThanks @phryneas! - Adds enhanced client awareness to the client.HttpLinkandBatchHttpLinkwill now per default send information about the client library you are using inextensions.This could look like this:
{ "query": "query GetUser($id: ID!) { user(id: $id) { __typename id name } }", "variables": { "id": 5 }, "extensions": { "clientLibrary": { "name": "@apollo/client", "version": "4.0.0" } } }This feature can be disabled by passing
enhancedClientAwareness: { transport: false }to yourApolloClient,HttpLinkorBatchHttpLinkconstructor options.
Minor Changes
-
#12698
be77d1aThanks @phryneas! - Adjusted the accept header for multipart requests according to the new GraphQL over HTTP spec with these changes:-multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json +multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/graphql-response+json,application/json;q=0.9-multipart/mixed;deferSpec=20220824,application/json +multipart/mixed;deferSpec=20220824,application/graphql-response+json,application/json;q=0.9 -
#12673
cee90abThanks @phryneas! - Add the newClientAwarenessLink.This link is already included in
HttpLinkandBatchHttpLinkto enable the "client awareness" and "enhanced client awareness" features, but you can also useClientAwarenessLinkdirectly in your link chain to combine it with other terminating links.If you want to save the bundle size that
ClientAwarenessLinkadds toHttpLinkandBatchHttpLink, you can useBaseHttpLinkorBaseBatchHttpLinkinstead. These links come without theClientAwarenessLinkincluded.For example:
import { ApolloClient, - HttpLink, } from "@apollo/client"; +import { BaseHttpLink } from "@apollo/client/link/http"; const client = new ApolloClient({ - link: new HttpLink({ + link: new BaseHttpLink({ uri, }), cache: new InMemoryCache(), }); -
#12698
be77d1aThanks @phryneas! - Adds anacceptoption toHttpOptionsthat allows to add additionalAcceptheaders to be merged in without overriding user-specified or default accept headers.
Patch Changes
-
- 4.0.0-alpha.2110 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12686
dc4b1d0Thanks @jerelmiller! - A@deferquery that has not yet finished streaming is now considered loading and thus theloadingflag will betrueuntil the response has completed. A newNetworkStatus.streamingvalue has been introduced and will be set as thenetworkStatuswhile the response is streaming. -
#12685
3b74800Thanks @jerelmiller! - Remove the check and warning forcache.fragmentMatcheswhen applying data masking.cache.fragmentMatchesis a required API and data masking may crash whencache.fragmentMatchesdoes not exist. -
#12684
e697431Thanks @jerelmiller! - RemovecontextfromuseLazyQueryhook options. If used,contextmust now be provided to theexecutefunction.contextwill reset to{}if not provided as an option toexecute.
-
- 4.0.0-alpha.206 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12675
8f1d974Thanks @phryneas! -ObservableQueryno longer has aqueryIdproperty.ApolloClient.getObservableQueriesno longer returns aMap<string, ObservableQuery>, but aSet<ObservableQuery>. -
#12647
a70fac6Thanks @phryneas! -ObservableQuerys will now only be registered with theApolloClientwhile they have subscribers.That means that
ApolloClient.getObservableQueriesandApolloClient.refetchQuerieswill only be able to return/refetch queries that have at least one subscriber.This changes the previous meaning of
activeandinactivequeries:inactivequeries are queries with a subscriber that are skipped from a React hook or have afetchPolicyofstandbyactivequeries are queries with at least one subscriber that are not skipped or instandby.
ObservableQuerys without subscribers but with an active ongoing network request (e.g. caused by callingreobserve) will be handled as if they had a subscriber for the duration of the query. -
#12678
91a876bThanks @jerelmiller! -queryRefs created bypreloadQueryno longer have a.toPromise()function. InsteadpreloadQuerynow has atoPromisefunction that accepts a queryRef and will resolve when the underlying promise has been resolved.const queryRef = preloadQuery(query, options); - await queryRef.toPromise(); + await preloadQuery.toPromise(queryRef); -
#12647
a70fac6Thanks @phryneas! -ApolloClient.stop()now cleans up more agressively to prevent memory leaks:- It will now unsubscribe all active
ObservableQueryinstances by emitting acompletedevent. - It will now reject all currently running queries with
"QueryManager stopped while query was in flight". - It will remove all queryRefs from the suspense cache.
- It will now unsubscribe all active
Minor Changes
-
- 4.0.0-alpha.195 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
- #12663
01512f2Thanks @jerelmiller! - Unsubscribing from anObservableQuerybefore a value has been emitted will remove the query from the tracked list of queries and will no longer be eligible for query deduplication.
Minor Changes
-
#12663
01512f2Thanks @jerelmiller! - Subscriptions created byclient.subscribe()can now be restarted. Restarting a subscription will terminate the connection with the link chain and recreate the request. Restarts also work across deduplicated subscriptions so callingrestarton anobservablewho's request is deduplicated will restart the connection for each observable.const observable = client.subscribe({ query: subscription }); // Restart the connection to the link observable.restart(); -
#12663
01512f2Thanks @jerelmiller! - Deduplicating subscription operations is now supported. Previously it was possible to deduplicate a subscription only if the new subscription was created before a previously subscribed subscription emitted any values. As soon as a value was emitted from a subscription, new subscriptions would create new connections. Deduplication is now active for as long as a subscription connection is open (i.e. the source observable hasn't emitted acompleteorerrornotification yet.)To disable deduplication and force a new connection, use the
queryDeduplicationoption incontextlike you would a query operation.As a result of this change, calling the
restartfunction returned fromuseSubscriptionwill now restart the connection on deduplicated subscriptions.
- #12663
- 4.0.0-alpha.185 Jun 2025pre-release
Release notes
Open source →Minor Changes
-
#12670
0a880eaThanks @phryneas! - Provide a mechanism to override the DataMasking types.Up until now, our types
Masked,MaskedDocumentNode,FragmentType,MaybeMaskedandUnmaskedwould assume that you are stictly using the type output format of GraphQL Codegen.With this change, you can now modify the behaviour of those types if you use a different form of codegen that produces different types for your queries.
A simple implementation that would override the
Maskedtype to remove all fields starting with_from a type would look like this:// your actual implementation of `Masked` type CustomMaskedImplementation<TData> = { [K in keyof TData as K extends `_${string}` ? never : K]: TData[K]; }; import { HKT } from "@apollo/client/utilities"; // transform this type into a higher kinded type that can be evaulated at a later time interface CustomMaskedType extends HKT { arg1: unknown; // TData return: CustomMaskedImplementation<this["arg1"]>; } // create an "implementation interface" for the types you want to override export interface CustomDataMaskingImplementation { Masked: CustomMaskedType; // other possible keys: `MaskedDocumentNode`, `FragmentType`, `MaybeMasked` and `Unmasked` }then you would use that
CustomDataMaskingImplementationinterface in your project to extend theDataMaskinginterface exported by@apollo/clientwith it's functionality:declare module "@apollo/client" { export interface DataMasking extends CustomDataMaskingImplementation {} }After that, all internal usage of
Maskedin Apollo Client as well as all usage in your code base will use the newCustomMaskedTypeimplementation.If you don't specify overrides, Apollo Client will still default to the GraphQL Codegen data masking implementation. The types for that are also explicitly exported as the
GraphQLCodegenDataMaskingnamespace in@apollo/client/masking.
Additional notes2 sources agree
Open source →Minor Changes
-
#12670
0a880eaThanks @phryneas! - Provide a mechanism to override the DataMasking types.Up until now, our types
Masked,MaskedDocumentNode,FragmentType,MaybeMaskedandUnmaskedwould assume that you are stictly using the type output format of GraphQL Codegen.With this change, you can now modify the behaviour of those types if you use a different form of codegen that produces different types for your queries.
A simple implementation that would override the
Maskedtype to remove all fields starting with_from a type would look like this:// your actual implementation of `Masked` type CustomMaskedImplementation<TData> = { [K in keyof TData as K extends `_${string}` ? never : K]: TData[K]; }; import { HKT } from "@apollo/client/utilities"; // transform this type into a higher kinded type that can be evaulated at a later time interface CustomMaskedType extends HKT { arg1: unknown; // TData return: CustomMaskedImplementation<this["arg1"]>; } // create an "implementation interface" for the types you want to override export interface CustomDataMaskingImplementation { Masked: CustomMaskedType; // other possible keys: `MaskedDocumentNode`, `FragmentType`, `MaybeMasked` and `Unmasked` }then you would use that
CustomDataMaskingImplementationinterface in your project to extend theTypeOverridesinterface exported by@apollo/clientwith it's functionality:declare module "@apollo/client" { export interface TypeOverrides extends CustomDataMaskingImplementation {} }After that, all internal usage of
Maskedin Apollo Client as well as all usage in your code base will use the newCustomMaskedTypeimplementation.If you don't specify overrides, Apollo Client will still default to the GraphQL Codegen data masking implementation. The types for that are also explicitly exported as the
GraphQLCodegenDataMaskingnamespace in@apollo/client/masking.
-
- 4.0.0-alpha.173 Jun 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12649
0be92adThanks @jerelmiller! - TheTDatageneric provided to types that return adataStateproperty is now modified by the givenDataStategeneric instead of passing a modifiedTDatatype. For example, aQueryRefthat could return partial data was defined asQueryRef<DeepPartial<TData>, TVariables>. NowTDatashould be provided unmodified and a set of allowed states should be given instead:QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'>.To migrate, use the following guide to replace your type with the right set of states (all types listed below are changed the same way):
- QueryRef<TData, TVariables> // `QueryRef`'s default is 'complete' | 'streaming' so this can also be left alone if you prefer // All other types affected by this change default to all states + QueryRef<TData, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming'> - QueryRef<TData | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'empty'> - QueryRef<DeepPartial<TData>, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'> - QueryRef<DeepPartial<TData> | undefined, TVariables> + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial' | 'empty'>The following types are affected. Provide the allowed
dataStatevalues to theTDataStategeneric:ApolloQueryResultQueryRefPreloadedQueryRefuseLazyQuery.ResultuseQuery.ResultuseReadQuery.ResultuseSuspenseQuery.Result
All
*QueryReftypes default tocomplete | streamingstates while the rest of the types default to'complete' | 'streaming' | 'partial' | 'empty'states. You shouldn't need to provide the states unless you need to either allow for partial data/empty values (*QueryRef) or a restricted set of states. -
#12649
0be92adThanks @jerelmiller! - Remove the deprecatedQueryReferencetype. Please useQueryRefinstead. -
#12633
9bfb51fThanks @phryneas! - If theexecutefunction ofuseLazyQueryis executed, previously started queries from the sameuseLazyQueryusage will be rejected with anAbortErrorunless.retain()is called on the promise returned by previousexecutecalls.Please keep in mind that
useLazyQueryis primarily meant as a means to synchronize your component to the status of a query and that it's purpose it not to make a series of network calls. If you plan on making a series of network calls without the need to synchronize the result with your component, consider usingApolloClient.queryinstead.
Minor Changes
-
#12633
9bfb51fThanks @phryneas! -ObservableQuery.refetchandObservableQuery.reobserveand theexecutefunction ofuseLazyQuerynow return aResultPromisewith an additional.retainmethod. If this method is called, the underlying network operation will be kept running even if theObservableQueryitself does not require the result anymore, and the Promise will resolve with the final result instead of resolving with an intermediate result in the case of early cancellation. -
#12649
0be92adThanks @jerelmiller! - Add a newdataStateproperty that determines the completeness of thedataproperty.dataStatehelps narrow the type ofdata.dataStateis now emitted fromObservableQueryand returned from all React hooks that return adataproperty.The
dataStatevalues are:empty: No data could be fulfilled from the cache or the result is incomplete.dataisundefined.partial: Some data could be fulfilled from the cache butdatais incomplete. This is only possible whenreturnPartialDataistrue.streaming:datais incomplete as a result of a deferred query and the result is still streaming in.complete:datais a fully satisfied query result fulfilled either from the cache or network.
Example:
const { data, dataState } = useQuery<TData>(query); if (dataState === "empty") { expectTypeOf(data).toEqualTypeOf<undefined>(); } if (dataState === "partial") { expectTypeOf(data).toEqualTypeOf<DeepPartial<TData>>(); } if (dataState === "streaming") { expectTypeOf(data).toEqualTypeOf<TData>(); } if (dataState === "complete") { expectTypeOf(data).toEqualTypeOf<TData>(); }
-
- 4.0.0-alpha.1628 May 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12644
fe2f005Thanks @jerelmiller! - Replace theresultproperty onServerErrorwithbodyText.bodyTextis set to the raw string body.HttpLinkandBatchHttpLinkno longer try and parse the response body as JSON when aServerErroris thrown. -
#12644
fe2f005Thanks @jerelmiller! - More strictly adhere to the GraphQL over HTTP spec. This change adds support for theapplication/graphql-response+jsonmedia type and modifies the behavior of theapplication/jsonmedia type.- The client will parse the response as a well-formed GraphQL response when the server encodes
content-typeusingapplication/graphql-response+jsonwith a non-200 status code. - The client will now throw a
ServerErrorwhen the server encodescontent-typeusingapplication/jsonand returns a non-200 status code. - The client will now throw a
ServerErrorwhen the server encodes using any othercontent-typeand returns a non-200 status code.
NOTE: If you use a testing utility to mock requests in your test, you may experience different behavior than production if your testing utility responds as
application/jsonbut your production server responds asapplication/graphql-response+json. If acontent-typeheader is not set, the client interprets the response asapplication/json. - The client will parse the response as a well-formed GraphQL response when the server encodes
-
#12644
fe2f005Thanks @jerelmiller! - Change the defaultAcceptheader toapplication/graphql-response+json,application/json;q=0.9. -
#12644
fe2f005Thanks @jerelmiller! -HttpLinkandBatchHttpLinkno longer emit anextnotification with the JSON-parsed response body when a well-formed GraphQL response is returned and aServerErroris thrown.
-
- 4.0.0-alpha.1523 May 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12639
1bdf489Thanks @jerelmiller! - Move internal testing utilities in@apollo/client/testingto@apollo/client/testing/internaland remove deprecated testing utilities. Some of the testing utilities exported from the@apollo/client/testingendpoint were not considered stable. As a result of this change, testing utilities or types exported from@apollo/client/testingare now considered stable and will not undergo breaking changes.The following APIs were removed. To migrate, update usages of the following APIs as such:
createMockClient- const client = createMockClient(data, query, variables); + const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]), + });mockObservableLink- const link = mockObservableLink(); + const link = new MockSubscriptionLink();mockSingleLink- const link = mockSingleLink({ - request: { query, variables }, - result: { data }, - }); + const link = new MockLink([ + { + request: { query, variables }, + result: { data }, + } + ]); -
#12637
d2a60d4Thanks @phryneas! -useQuery: only advancepreviousDataifdataactually changed -
#12631
b147cacThanks @phryneas! -ObservableQuerywill now return aloading: falsestate forfetchPolicystandby, even before subscription -
#12639
1bdf489Thanks @jerelmiller! - Remove the@apollo/client/testing/coreentrypoint in favor of@apollo/client/testing.
Minor Changes
-
#12639
1bdf489Thanks @jerelmiller! - MoveMockLinktypes toMockLinknamespace. This affects theMockedResponse,MockLinkOptions, andResultFunctiontypes. These types are still exported but are deprecated in favor of the namespace. To migrate, use the types on theMockLinknamespace instead.import { - MockedResponse, - MockLinkOptions, - ResultFunction, + MockLink } from "@apollo/client/testing"; - const mocks: MockedResponse = []; + const mocks: MockLink.MockedResponse = []; - const result: ResultFunction = () => {/* ... */ } + const result: MockLink.ResultFunction = () => {/* ... */ } - const options: MockLinkOptions = {} + const options: MockLink.Options = {}
Patch Changes
-
- 4.0.0-alpha.1421 May 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12614
d2851e2Thanks @jerelmiller! - ThegetCacheKeyfunction is no longer available fromoperation.getContext()in the link chain. Useoperation.client.cache.identify(obj)in the link chain instead. -
#12556
c3fcedaThanks @phryneas! -ObservableQuerywill now keep previousdataaround when emitting aloadingstate, unlessqueryorvariableschanged. Note that@exportsvariables are not taken into account for this, sodatawill stay around even if they change. -
#12556
c3fcedaThanks @phryneas! - RemovedgetLastResult,getLastErrorandresetLastResultsfromObservableQuery -
#12614
d2851e2Thanks @jerelmiller! - Removes theresolversoption fromApolloClient. Local resolvers have instead been moved to the newLocalStateinstance which is assigned to thelocalStateoption inApolloClient. To migrate, move theresolversvalues into aLocalStateinstance and assign that instance tolocalState.new ApolloClient({ - resolvers: { /* ... */ } + localState: new LocalState({ + resolvers: { /* ... */ } + }), }); -
#12614
d2851e2Thanks @jerelmiller! - Remove local resolvers APIs fromApolloClientin favor oflocalState. Methods removed are:addResolversgetResolverssetResolverssetLocalStateFragmentMatcher
-
#12614
d2851e2Thanks @jerelmiller! - Third-party caches must now implement thefragmentMatchesAPI. AdditionallyfragmentMatchesmust be able to handle bothInlineFragmentNodeandFragmentDefinitionNodenodes.class MyCache extends ApolloCache { // This is now required public fragmentMatches( fragment: InlineFragmentNode | FragmentDefinitionNode, typename: string ): boolean { return; // ... logic to determine if typename matches fragment } } -
#12556
c3fcedaThanks @phryneas! - Reworked the logic for then a loading state is triggered. If the link chain responds synchronously, a loading state will be omitted, otherwise it will be triggered. If local resolvers are used, the time window for "sync vs async" starts as soon as@exportsvariables are resolved. -
#12556
c3fcedaThanks @phryneas! - Dropped thesaveAsLastResultargument fromObservableQuery.getCurrentResult -
#12614
d2851e2Thanks @jerelmiller! - The resolver function'scontextargument (the 3rd argument) has changed to provide additional information without the possibility of name clashes. Previously thecontextargument would spread request context and override theclientandcacheproperties to give access to both inside of a resolver. Thecontextargument takes now takes the following shape:{ // the request context. By default `TContextValue` is of type `DefaultContext`, // but can be changed if a `context` function is provided. requestContext: TContextValue, // The client instance making the request client: ApolloClient, // Whether the resolver is run as a result of gathering exported variables // or resolving the value as part of the result phase: "exports" | "resolve" }To migrate, pull any request context from
requestContextand thecachefrom theclientproperty:new LocalState({ resolvers: { Query: { - myResolver: (parent, args, { someValue, cache }) => { + myResolver: (parent, args, { requestContext, client }) => { + const someValue = requestContext.someValue; + const cache = client.cache; } } } }); -
#12614
d2851e2Thanks @jerelmiller! - Apollo Client no longer ships with support for@clientfields out-of-the-box and now must be opt-in. To opt in to use@clientfields, pass an instantiatedLocalStateinstance to thelocalStateoption. If a query contains@clientand local state hasn't been configured, an error will be thrown.import { LocalState } from "@apollo/client/local-state"; new ApolloClient({ localState: new LocalState(), }); -
#12614
d2851e2Thanks @jerelmiller! - Remove thefragmentMatcheroption fromApolloClient. Custom fragment matchers used with local state are no longer supported. Fragment matching is now performed by the configuredcachevia thecache.fragmentMatchesAPI. -
#12556
c3fcedaThanks @phryneas! - A call toObservableQuery.setVariableswith different variables or aObservableQuery.refetchcall will always now guarantee that a value will be emitted from the observable, even if it is deep equal to the previous value.
Minor Changes
-
#12614
d2851e2Thanks @jerelmiller! - Revamp local resolvers and fix several issues from the existingresolversoption.- Throwing errors in a resolver will set the field value as
nulland add an error to the response'serrorsarray. - Remote results are dealiased before they are passed as the parent object to a resolver so that you can access fields by their field name.
- You can now specify a
contextfunction that you can use to customize therequestContextgiven to resolvers. - The
LocalStateclass accepts aResolversgeneric that provides autocompletion and type checking against your resolver types to ensure your resolvers are type-safe. data: nullis now handled correctly and does not call your local resolvers when the server does not provide a result.- Additional warnings have been added to provide hints when resolvers behave unexpectedly.
import { LocalState } from "@apollo/client/local-state"; import { Resolvers } from "./path/to/local-resolvers-types.ts"; // LocalState now accepts a `Resolvers` generic. const localState = new LocalState<Resolvers>({ // The return value of this funciton context: (options) => ({ // ... }), resolvers: { // ... }, }); // You may also pass a `ContextValue` generic used to ensure the `context` // function returns the correct type. This type is inferred from your resolvers // if not provided. new LocalState<Resolvers, ContextValue>({ // ... }); - Throwing errors in a resolver will set the field value as
-
- 4.0.0-alpha.1314 May 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12600
34ff6aaThanks @jerelmiller! - Move most of the utilities in@apollo/client/utilitiesto@apollo/client/utilities/internal. Many of the utilities exported from the@apollo/client/utilitiesendpoint were not considered stable.As a result of this change, utilities or types exported from
@apollo/client/utilitiesare now documented and considered stable and will not undergo breaking changes. -
#12595
60bb49cThanks @jerelmiller! - Remove the@apollo/client/testing/experimentaltest utilities. Use GraphQL Testing Library instead.
Patch Changes
- #12618
e4a3ecfThanks @jerelmiller! - Remove code that strips@clientfields inHttpLinkandBatchHttpLink. This was unused code since core handles removing@clientfields and should have no observable change.
-
- 4.0.0-alpha.1229 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12586
605db8eThanks @jerelmiller! - Remove thetypeDefsoption fromApolloClient. -
#12588
eed825aThanks @jerelmiller! - RemoveTContextgeneric argument from all types that use it.TContextis replaced withDefaultContextwhich can be modified using declaration merging. -
#12590
a005e82Thanks @jerelmiller! - Dropgraphqlv15 as a valid peer dependency. -
#12591
a7e7383Thanks @jerelmiller! - Rename the@apollo/client/link/coreentrypoint to@apollo/client/link. -
#12589
15f5a1cThanks @jerelmiller! - Require thelinkoption when instantiatingApolloClient. This removes theuri,credentialsandheadersoptions fromApolloClientin favor of passing an instantiatedHttpLinkdirectly. To migrate:If using
uri,credentials, orheadersoptionsnew ApolloClient({ // ... - uri, - credentials, - headers, + link: new HttpLink({ uri, credentials, headers }), // or if you prefer the function call approach: + link: createHttpLink({ uri, credentials, headers }), });If creating a client without the
linkoptionnew ApolloClient({ // ... + link: ApolloLink.empty() });
-
- 4.0.0-alpha.1123 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12576
a92ff78Thanks @jerelmiller! - ThecacheandforceFetchproperties are no longer available on context when callingoperation.getContext().cachecan be accessed through theoperationwithoperation.client.cacheinstead.forceFetchhas been replaced withqueryDeduplicationwhich specifies whetherqueryDeduplicationwas enabled for the request or not. -
#12576
a92ff78Thanks @jerelmiller! -ApolloLink.executenow requires a third argument which provides theclientthat initiated the request to the link chain. If you useexecutedirectly, add a third argument with aclientproperty:ApolloLink.execute(link, operation, { client }); // or if you import the `execute` function directly: execute(link, operation, { client }); -
#12566
ce4b488Thanks @jerelmiller! - Don'tbroadcastQuerieswhen a query is torn down.
Minor Changes
-
#12576
a92ff78Thanks @jerelmiller! - Provide an extension to define types forcontextpassed to the link chain. To define your own types, use declaration merging to add properties to theDefaultContexttype.// @apollo-client.d.ts // This import is necessary to ensure all Apollo Client imports // are still available to the rest of the application. import "@apollo/client"; declare module "@apollo/client" { interface DefaultContext extends Record<string, any> { myProperty: string; } }Links that provide context options can be used with this type to add those context types to
DefaultContext. For example, to add context options fromHttpLink, add the following code:import { HttpLink } from "@apollo/client"; declare module "@apollo/client" { interface DefaultContext extends HttpLink.ContextOptions { myProperty: string; } }At this time, the following built-in links support context options:
HttpLink.ContextOptionsBatchHttpLink.ContextOptions
-
#12576
a92ff78Thanks @jerelmiller! - Add aclientproperty to theoperationpassed to the link chain. Thisclientis set as theclientmaking the request to the link chain.
Patch Changes
-
#12574
0098ec9Thanks @jerelmiller! - Exportgqlfrom the@apollo/client/reactentrypoint. -
#12572
3dc50e6Thanks @jerelmiller! - AdjustuseMutationtypes to better handle required variables. When required variables are missing, TypeScript will now complain if they are not provided either to the hook or the returnedmutatefunction. Providing required variables touseMutationwill make them optional in the returnedmutatefunction.
-
- 4.0.0-alpha.1017 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12559
49ace0eThanks @jerelmiller! -ObservableQuery.variablescan now be reset back to empty when callingreobservewithvariables: undefined. Previously thevariableskey would be ignored sovariableswould remain unchanged. -
#12559
49ace0eThanks @jerelmiller! -neveris no longer supported as a validTVariablesgeneric argument for APIs that requirevariablesas part of its type. UseRecord<string, never>instead. -
#12559
49ace0eThanks @jerelmiller! - When passing avariableskey with the valueundefined, the value will be replaced by the default value in the query, if it is provided, rather than leave it asundefined.// given this query const query = gql` query PaginatedQuery($limit: Int! = 10, $offset: Int) { list(limit: $limit, offset: $offset) { id } } `; const observable = client.query({ query, variables: { limit: 5, offset: 0 }, }); console.log(observable.variables); // => { limit: 5, offset: 0 } observable.reobserve({ variables: { limit: undefined, offset: 10 } }); // limit is now `10`. This would previously be `undefined` console.log(observable.variables); // => { limit: 10, offset: 10 } -
#12562
90bf0e6Thanks @jerelmiller! -client.queryno longer supports afetchPolicyofstandby.standbydoes not fetch and did not returndata.standbyis meant for watched queries where fetching should be on hold.
Minor Changes
-
#12557
51d26aeThanks @jerelmiller! - Add ability to specify message formatter forCombinedGraphQLErrorsandCombinedProtocolErrors. To provide your own message formatter, override the staticformatMessageproperty on these classes.CombinedGraphQLErrors.formatMessage = ( errors, { result, defaultFormatMessage } ) => { return "Some formatted message"; }; CombinedProtocolErrors.formatMessage = (errors, { defaultFormatMessage }) => { return "Some formatted message"; }; -
#12546
5dffbbeThanks @jerelmiller! - Add a staticismethod to error types defined by Apollo Client.ismakes it simpler to determine whether an error is a specific type, which can be helpful in cases where you'd like to narrow the error type in order to use specific properties from that error.This change applies to the following error types:
CombinedGraphQLErrorsCombinedProtocolErrorsServerErrorServerParseErrorUnconventionalError
Example
import { CombinedGraphQLErrors } from "@apollo/client"; if (CombinedGraphQLErrors.is(error)) { console.log(error.message); error.errors.forEach((graphQLError) => console.log(graphQLError.message)); } -
#12561
99d72bfThanks @jerelmiller! - Add the ability to detect if an error was an error was emitted from the link chain. This is useful if your application throws custom errors in other areas of the application and you'd like to differentiate them from errors emitted by the link chain itself.To detect if an error was emitted from the link chain, use
LinkError.is.import { LinkError } from "@apollo/client"; client.query({ query }).catch((error) => { if (LinkError.is(error)) { // This error originated from the link chain } });
Patch Changes
-
#12559
49ace0eThanks @jerelmiller! - Thevariablesoption used with various APIs are now enforced more consistently across the client whenTVariablescontains required variables. If requiredvariablesare not provided, TypeScript will now complain that it requires avariablesoption.This change affects the following APIs:
client.queryclient.mutateclient.subscribeclient.watchQueryuseBackgroundQueryuseQueryuseSubscriptionuseSuspenseQuery
-
#12559
49ace0eThanks @jerelmiller! - Fix type ofvariablesreturned fromuseLazyQuery. Whencalledisfalse,variablesis nowPartial<TVariables>instead ofTVariables. -
#12562
90bf0e6Thanks @jerelmiller! -client.queryno longer supportsnotifyOnNetworkStatusChangein options. An error will be thrown if this option is set. The effects of this option were not observable byclient.querysinceclient.queryemits a single result. -
#12557
51d26aeThanks @jerelmiller! - Update format of the error message forCombinedGraphQLErrorsandCombinedProtocolErrorsto be more like v3.x.console.log(error.message); - `The GraphQL server returned with errors: - - Email not found - - Username already in use` + `Email not found + Username already in use` -
#12559
49ace0eThanks @jerelmiller! -ObservableQuery.variableshas been updated to returnTVariablesrather thanTVariables | undefined. This is more consistent with the runtime value where an empty object ({}) will be returned when thevariablesoption is not provided.
-
- 4.0.0-alpha.911 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12536
e14205aThanks @jerelmiller! - An initial loading state is now emitted fromObservableQuerywhen subscribing ifnotifyOnNetworkStatusChangeis set totrue. -
#12512
e809b71Thanks @jerelmiller! -notifyOnNetworkStatusChangenow defaults totrue. This means that loading states will be emitted (core API) or rendered (React) by default when callingrefetch,fetchMore, etc. To maintain the old behavior, setnotifyOnNetworkStatusChangetofalseindefaultOptions.new ApolloClient({ defaultOptions: { watchQuery: { // Use the v3 default notifyOnNetworkStatusChange: false, }, }, });
Patch Changes
-
#12536
e14205aThanks @jerelmiller! - The returnednetworkStatusinuseLazyQueryis now set tosetVariableswhen calling theuseLazyQueryexecutefunction for the first time with variables. -
#12536
e14205aThanks @jerelmiller! - EnsureObservableQuerystops polling if switching to astandbyfetchPolicy. When switching back to a non-standbyfetchPolicy, polling will resume. -
#12536
e14205aThanks @jerelmiller! - Ensure a loading state is emitted when calling theexecutefunction after changing clients inuseLazyQuery. -
#12542
afb4fceThanks @jerelmiller! - EnsureuseLazyQuerydoes not return apartialproperty which is not specified by the result type.
-
- 4.0.0-alpha.810 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12539
dd0d6d6Thanks @jerelmiller! -onErrorlink now uses a singleerrorproperty to report the error that caused the link callback to be called. This will be an instance ofCombinedGraphQLErrorsin the event GraphQL errors were emitted from the terminating link,CombinedProtocolErrorsif the terminating link emitted protocol errors, or the unwrapped error type if any other non-GraphQL error was thrown or emitted.- const errorLink = onError(({ graphQLErrors, networkError, protocolErrors }) => { - graphQLErrors.forEach(error => console.log(error.message)); + const errorLink = onError(({ error }) => { + if (error.name === 'CombinedGraphQLErrors') { + error.errors.forEach(rawError => console.log(rawError.message)); + } }); -
#12533
73221d8Thanks @jerelmiller! - Remove theonErrorandsetOnErrormethods fromApolloLink.onErrorwas only used byMockLinkto rewrite errors ifsetOnErrorwas used. -
#12531
7784b46Thanks @jerelmiller! - Mocked responses passed toMockLinknow accept a callback for therequest.variablesoption. This is used to determine if the mock should be matched for a set of request variables. With this change, thevariableMatcheroption has been removed in favor of passing a callback tovariables. Update by moving the callback function fromvariableMatchertorequest.variables.new MockLink([ { request: { query, + variables: (requestVariables) => true }, - variableMatcher: (requestVariables) => true } ]); -
#12526
391af1dThanks @phryneas! - The@apollo/clientand@apollo/client/coreentry points are now equal. In the next major, the@apollo/client/coreentry point will be removed. Please change imports over from@apollo/client/coreto@apollo/client. -
#12525
8785186Thanks @jerelmiller! - Throw an error when a client-only query is used in a mocked response passed toMockLink. -
#12532
ae0dcadThanks @jerelmiller! - Default thedelayfor all mocked responses passed toMockLinkusingrealisticDelay. This ensures your test handles loading states by default and is not reliant on a specific timing.If you would like to restore the old behavior, use a global default delay of
0.MockLink.defaultOptions = { delay: 0, }; -
#12530
2973e2aThanks @jerelmiller! - RemovenewDataoption for mocked responses passed toMockLinkor themocksoption onMockedProvider. This option was undocumented and was nearly identical to using theresultoption as a callback.To replicate the old behavior of
newData, useresultas a callback and add themaxUsageCountoption with a value set toNumber.POSITIVE_INFINITY.with
MockLinknew MockLink([ { request: { query, variables }, - newData: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }), + result: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }), + maxUsageCount: Number.POSITIVE_INFINITY, } ])with
MockedProvider<MockedProvider mocks={[ { request: { query, variables }, - newData: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }), + result: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }), + maxUsageCount: Number.POSITIVE_INFINITY, } ]} />
Minor Changes
-
#12532
ae0dcadThanks @jerelmiller! - Allow mocked responses passed toMockLinkto accept a callback for thedelayoption. Thedelaycallback will be given the current operation which can be used to determine what delay should be used for the mock. -
#12532
ae0dcadThanks @jerelmiller! - Introduce a newrealisticDelayhelper function for use with thedelaycallback for mocked responses used withMockLink.realisticDelaywill generate a random value between 20 and 50ms to provide an experience closer to unpredictable network latency.realisticDelaycan be configured with aminandmaxto set different thresholds if the defaults are not sufficient.import { realisticDelay } from "@apollo/client/testing"; new MockLink([ { request: { query }, result: { data: { greeting: "Hello" } }, delay: realisticDelay(), }, { request: { query }, result: { data: { greeting: "Hello" } }, delay: realisticDelay({ min: 10, max: 100 }), }, ]); -
#12532
ae0dcadThanks @jerelmiller! - Add ability to specify a defaultdelayfor all mocked responses passed toMockLink. Thisdelaycan be configured globally (all instances ofMockLinkwill use the global defaults), or per-instance (all mocks in a single instance will use the defaults). Adelaydefined on a single mock will supercede all default delays. Per-instance defaults supercede global defaults.Global defaults
MockLink.defaultOptions = { // Use a default delay of 20ms for all mocks in all instances without a specified delay delay: 20, // altenatively use a callback which will be executed for each mock delay: () => getRandomNumber(), // or use the built-in `realisticDelay`. This is the default delay: realisticDelay(), };Per-instance defaults
new MockLink( [ // Use the default delay { request: { query }, result: { data: { greeting: "Hello" } }, }, { request: { query }, result: { data: { greeting: "Hello" } }, // Override the default for this mock delay: 10, }, ], { defaultOptions: { // Use a default delay of 20ms for all mocks without a specified delay delay: 20, // altenatively use a callback which will be executed for each mock delay: () => getRandomNumber(), // or use the built-in `realisticDelay`. This is the default delay: realisticDelay(), }, } );
-
- 4.0.0-alpha.73 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12513
9c3207cThanks @phryneas! - Removed the@apollo/client/react/contextand@apollo/client/react/hooksentry points. Please use@apollo/client/reactinstead. -
#12513
9c3207cThanks @phryneas! - Removed the@apollo/client/react/parserentry point. There is no replacement.
Patch Changes
-
- 4.0.0-alpha.61 Apr 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12485
d338303Thanks @jerelmiller! - Throw an error for queries and mutations if the link chain completes without emitting a value. -
#12484
9a8b9ceThanks @jerelmiller! - Removeloading,networkStatus, andpartialproperties on all promise-based query APIs. These properties were mostly static and were unnecessary since promise resolution guaranteed that the query was not longer loading.This affects the following APIs:
client.queryclient.refetchQueriesclient.reFetchObservableQueriesclient.resetStoreobservableQuery.fetchMoreobservableQuery.refetchobservableQuery.reobserveobservableQuery.setVariables- The
useLazyQueryexecutefunction
Minor Changes
-
#12497
ff2cbe1Thanks @jerelmiller! - Add adataproperty toCombinedGraphQLErrorsthat captures any partial data returned by the GraphQL response whenerrorsare also returned. -
#12488
c98b633Thanks @phryneas! - Add a new method for static SSR of React components,prerenderStatic. The old methods,getDataFromTree,getMarkupFromTreeandrenderToStringWithDatahave been deprecated in favor ofprerenderStatic.If used with React 19 and the
prerenderorprerenderToNodeStreamapis fromreact-dom/static, this method can now be used to SSR-prerender suspense-enabled hook APIs.
-
- 4.0.0-alpha.531 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12478
5ea6a45Thanks @jerelmiller! - Removevariablesfrom the result returned fromuseSubscription. -
#12476
6afff60Thanks @jerelmiller! - Subscriptions now emit aSubscribeResultinstead of aFetchResult. As a result, theerrorsfield has been removed in favor oferror. -
#12475
3de63ebThanks @jerelmiller! - Unify error behavior on mutations for GraphQL errors and network errors by ensuring network errors are subject to theerrorPolicy. Network errors created when using anerrorPolicyofallwill now resolve the promise and be returned on theerrorproperty of the result, or stripped away when theerrorPolicyisnone. -
#12475
3de63ebThanks @jerelmiller! -client.mutatenow returns aMutateResultinstead ofFetchResult. As a result, theerrorsproperty has been removed in favor oferrorwhich is set if either a network error occured or GraphQL errors are returned from the server.useMutationnow also returns aMutateResultinstead of aFetchResult. -
#12475
3de63ebThanks @jerelmiller! - Mutations no longer report errors if the GraphQL result from the server contains an empty array of errors. -
#12476
6afff60Thanks @jerelmiller! - Unify error behavior on subscriptions for GraphQL errors and network errors by ensuring network errors are subject to theerrorPolicy. Network errors that terminate the connection will now be emitted on theerrorproperty passed to thenextcallback followed by a call to thecompletecallback. -
#12478
5ea6a45Thanks @jerelmiller! - Remove deprecatedonSubscriptionDataandonSubscriptionCompletecallbacks fromuseSubscription. UseonDataandonCompleteinstead. -
#12476
6afff60Thanks @jerelmiller! - GraphQL errors or network errors emitted while using anerrorPolicyofignorein subscriptions will no longer emit a result if there is nodataemitted along with the error. -
#12476
6afff60Thanks @jerelmiller! - Subscriptions no longer emit errors in theerrorcallback and instead provide errors on theerrorproperty on the result passed to thenextcallback. As a result, errors will no longer automatically terminate the connection allowing additional results to be emitted when the connection stays open.When an error terminates the downstream connection, a
nextevent will be emitted with anerrorproperty followed by acompleteevent instead.
Minor Changes
- #12487
b695e5eThanks @phryneas! - Split out SSR-specific code from useQuery hook, remove RenderPromises
Patch Changes
-
#12487
b695e5eThanks @phryneas! -useQuerywithssr: false- previously,skiphad a higher priortity thanssr: falsewhilessr: falsehad a higher priority thanfetchPolicy: "standby"(which is roughly equivalent toskip).This priority has been adjusted so now both
skipandfetchPolicy: "standby"have a higher priority thanssr: falseand will returnloading: false, whilessr: falsewill only come after those and will returnloading: trueif those are not set. -
#12475
3de63ebThanks @jerelmiller! - Fix an issue where passingonErrortouseMutationwould resolve the promise returned by themutatefunction instead of rejecting when using anerrorPolicyofnone. -
#12475
3de63ebThanks @jerelmiller! - Fix an issue where additional response properties were returned on the result returned fromclient.mutate, such as@deferpayload fields. These properties are now stripped out to correspond to the TypeScript type.
-
- 4.0.0-alpha.424 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12463
3868df8Thanks @jerelmiller! -ObservableQuery.setOptionshas been removed as it was an alias ofreobserve. Prefer usingreobservedirectly instead.const observable = client.watchQuery(options); // Use reobserve to set new options and reevaluate the query - observable.setOptions(newOptions); + observable.reobserve(newOptions);As a result of this change,
reobservehas been marked for public use and is no longer considered an internal API. ThenewNetworkStatusargument has been removed to facilitate this change. -
#12470
d32902fThanks @phryneas! -ssrMode,ssrForceFetchDelayanddisableNetworkFetcheshave been reworked:Previously, a
ObservableQuerycreated byclient.queryorclient.watchQuerywhile one of those were active would permanently be changed from afetchPolicyof"network-only"or"cache-and-network"to"cache-first", and stay that way even long afterdisableNetworkFetcheswould have been deactivated.Now, the
ObservableQuerywill keep their originalfetchPolicy, but queries made duringdisableNetworkFetcheswill just apply thefetchPolicyreplacement at request time, just for that one request.ApolloClient.disableNetworkFetcheshas been renamed toApolloClient.prioritizeCacheValuesto better reflect this behaviour. -
#12465
a132163Thanks @jerelmiller! - Flatten out React hook types. As a result, the base types have been removed. Prefer using the hook types instead. Removed types include:BaseMutationOptionsBaseQueryOptionsBaseSubscriptionOptionsObservableQueryFieldsMutationSharedOptionsQueryFunctionOptions
-
#12463
3868df8Thanks @jerelmiller! -useQueryno longer returnsreobserveas part of its result. It was possible to usereobserveto set new options on the underlyingObservableQueryinstance which differed from the options passed to the hook. This could result in unexpected results. Instead prefer to rerender the hook with new options.
Patch Changes
- #12465
a132163Thanks @jerelmiller! - Rename all React hook result types and options. These types have all moved under a namespace that matches the hook name. For example,useQueryexportsuseQuery.OptionsanduseQuery.Resulttypes. As such, the old hook types have been deprecated and will be removed in v5.
-
- 4.0.0-alpha.320 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12457
32e85eaThanks @jerelmiller! - Network errors triggered by queries now adhere to theerrorPolicy. This means that GraphQL errors and network errors now behave the same way. Previously promise-based APIs, such asclient.query, would reject the promise with the network error even iferrorPolicywas set toignore. The promise is now resolved with theerrorproperty set to the network error instead. -
#12464
0595f39Thanks @jerelmiller! - Remove thecalledproperty fromuseQuery.
-
- 4.0.0-alpha.219 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12450
876d070Thanks @jerelmiller! - RemoveTSerializedgeneric argument toApolloCache. TheApolloCachebase cache abstraction now returnsunknownforcache.extractwhich can be overridden by a cache subclass. -
#12450
876d070Thanks @jerelmiller! - Remove theTCacheShapegeneric argument toApolloClient.client.extract()now returnsunknownby default. You will either need to type-cast this to the expected serialized shape, or use thecache.extract()directly from the subclass to get more specific types. -
#12446
ab920d2Thanks @jerelmiller! - Removes thedefaultOptionsoption fromuseQuery. Use options directly or use the globalApolloClientdefaultOptions. -
#12442
c5ead08Thanks @jerelmiller! - Remove the deprecatedcanonizeResultsoption. It was prone to memory leaks. As such, some results that were referentially equal whencanonizeResultsoption was set totrueno longer retain the same object identity. -
#12442
c5ead08Thanks @jerelmiller! - RemoveresetResultIdentitiesoption fromInMemoryCache.gc(). This affected object canonization which has been removed. -
#12451
77e1b13Thanks @jerelmiller! - Default theTDatageneric type tounknownin all APIs that use aTDatageneric argument such asuseQuery,client.query, etc.
Patch Changes
-
- 4.0.0-alpha.114 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12433
b86e50bThanks @phryneas! - Remove workarounds for streaming with non-WhatWG response bodies to reduce bundle size.This removes support for
fetchimplementations that return Node Streams, Async Iterators or Blob instances asResponse.body.In the WhatWG Fetch specification,
Response.bodyis specified as a WhatWG ReadableStream.At this point in time, this is natively supported in browsers,
nodeand React Native (via react-native-fetch-api, see our setup instructions for React Native).If you are using an older
fetchpolyfill that deviates from the spec, this might not be compatible - for example, node-fetch returns a nodeReadableinstead of aReadableStream. In those cases, please switch to a compatible alternative such as thenode-nativefetch, orundici.
Minor Changes
-
#12438
5089516Thanks @phryneas! - Droprehacktdependency. We can now directly import fromreactwithout causing build errors in RSC. -
#12437
4779dc7Thanks @phryneas! - Remove polyfills for Object.freeze,seal and preventExtensions in React NativeThese polyfills were only necessary until React Native 0.59, which patched the problem on the React Native side.
With React Native 0.61, the
Mapfunction was completely replaced with a native implementation that never had the problems we guarded against. -
#12438
5089516Thanks @phryneas! - Addreact-serverentry point with stubs for normal exports.
-
- 4.0.0-alpha.013 Mar 2025pre-release
Release notes3 sources agree
Open source →Major Changes
-
#12384
6aa6fd3Thanks @jerelmiller! - Remove theasyncMaputility function. Instead use one of the RxJS operators that creates Observables from promises, such asfrom. -
#12398
8cf5077Thanks @jerelmiller! - Removes theisApolloErrorutility function to check if the error object is anApolloErrorinstance. Useinstanceofto check for more specific error types that replaceApolloError. -
#12379
ef892b4Thanks @jerelmiller! - Removes theaddTypenameoption fromInMemoryCacheandMockedProvider.__typenameis now always added to the outgoing query document when usingInMemoryCacheand cannot be disabled.If you are using
<MockedProvider />withaddTypename={false}, ensure that your mocked responses include a__typenamefield. This will ensure cache normalization kicks in and behaves more like production. -
#12396
00f3d0aThanks @jerelmiller! - Remove the deprecatederrorsproperty fromuseQueryanduseLazyQuery. Read errors from theerrorproperty instead. -
#12222
d1a9054Thanks @jerelmiller! - Drop support for React 16. -
#12376
a0c996aThanks @jerelmiller! - Remove deprecatedignoreResultsoption fromuseMutation. If you don't want to synchronize component state with the mutation, useuseApolloClientto access your client instance and useclient.mutatedirectly. -
#12384
6aa6fd3Thanks @jerelmiller! - Unusubscribing fromObservableQuerywhile a request is in flight will no longer terminate the request by unsubscribing from the link observable. -
#12367
e6af35eThanks @jerelmiller! - ThepreviousDataproperty onuseLazyQuerywill now change only whendatachanges. PreviouslypreviousDatawould change to the same value asdatawhile the query was loading. -
#12224
51e6c0fThanks @jerelmiller! - Remove deprecatedpartialRefetchoption. -
#12407
8b1390bThanks @jerelmiller! - Callingrefetchwith new variables will now set thenetworkStatustorefetchinstead ofsetVariables. -
#12384
6aa6fd3Thanks @jerelmiller! - Remove theiterateObserversSafelyutility function. -
#12398
8cf5077Thanks @jerelmiller! - Apollo Client no longer wraps errors inApolloError.ApolloErrorhas been replaced with separate error classes depending on the cause of the error. As such, APIs that return anerrorproperty have been updated to use the genericErrortype. Useinstanceofto check for more specific error types.Migration guide
ApolloErrorencapsulated 4 main error properties. The type of error would determine which property was set:graphqlErrors- Errors returned from theerrorsfield by the GraphQL servernetworkError- Any non-GraphQL error that caused the query to failprotocolErrors- Transport-level errors that occur during multipart HTTP subscriptionsclientErrors- A space to define custom errors. Mostly unused.
These errors were mutally exclusive, meaning both
networkErrorandgraphqlErrorswere never set simultaneously. The following replaces each of these fields fromApolloError.graphqlErrorsGraphQL errors are now encapsulated in a
CombinedGraphQLErrorsinstance. You can access the raw GraphQL errors via theerrorsproperty.import { CombinedGraphQLErrors } from "@apollo/client"; // ... const { error } = useQuery(query); if (error && error instanceof CombinedGraphQLErrors) { console.log(error.errors); }networkErrorNetwork errors are no longer wrapped and are instead passed through directly.
const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { observer.error(new Error("Test error")); }); }), }); // ... const { error } = useQuery(query); // error is `new Error('Test error')`;protocolErrorsProtocol errors are now encapsulated in a
CombinedProtocolErrorsinstance. You can access the raw protocol errors via theerrorsproperty.import { CombinedProtocolErrors } from "@apollo/client"; // ... const { error } = useSubscription(subscription); if (error && error instanceof CombinedProtocolErrors) { console.log(error.errors); }clientErrorsThese were unused by the client and have no replacement. Any non-GraphQL or non-protocol errors are now passed through unwrapped.
Strings as errors
If the link sends a string error, Apollo Client will wrap this in an
Errorinstance. This ensureserrorproperties are guaranteed to be of typeError.const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { // Oops we sent a string instead of wrapping it in an `Error` observer.error("Test error"); }); }), }); // ... const { error } = useQuery(query); // The error string is wrapped and returned as `new Error('Test error')`;Non-error types
If the link chain sends any other object type as an error, Apollo Client will wrap this in an
UnknownErrorinstance with thecauseset to the original object. This ensureserrorproperties are guaranteed to be of typeError.const client = new ApolloClient({ link: new ApolloLink(() => { return new Observable((observer) => { observer.error({ message: "Not a proper error type" }); }); }), }); // ... const { error } = useQuery(query); // error is an `UnknownError` instance. error.cause returns the original object. -
#12384
6aa6fd3Thanks @jerelmiller! - RemovefromErrorutility function. UsethrowErrorinstead. -
#12211
c2736dbThanks @jerelmiller! - Remove the deprecatedgraphql,withQuery,withMutation,withSubscription, andwithApollohoc components. Use the provided React hooks instead. -
#12262
10ef733Thanks @jerelmiller! - RemoveitAsynctest utility. -
#12398
8cf5077Thanks @jerelmiller! - Updates theServerErrorandServerParseErrortypes to be properErrorsubclasses. Perviously these were plainErrorintances with additional properties added at runtime. All properties are retained, butinstanceofchecks now work correctly.import { ServerError, ServerParseError } from "@apollo/client"; if (error instanceof ServerError) { // ... } if (error instanceof ServerParseError) { // ... } -
#12367
e6af35eThanks @jerelmiller! -useLazyQueryno longer supports SSR environments and will now throw if theexecutefunction is called in SSR. If you need to run a query in an SSR environment, useuseQueryinstead. -
#12367
e6af35eThanks @jerelmiller! - The execute function returned fromuseLazyQuerynow only supports thecontextandvariablesoptions. This means that passing options supported by the hook no longer override the hook value.To change options, rerender the component with new options. These options will take effect with the next query execution.
-
#12384
6aa6fd3Thanks @jerelmiller! -ObservableQuerywill no longer terminate on errors and will instead emit anextvalue with anerrorproperty. This ensures thatObservableQueryinstances can continue to receive updates after errors are returned in requests without the need to resubscribe to the observable. -
#12398
8cf5077Thanks @jerelmiller! - Removes thethrowServerErrorutility function. Now thatServerErroris anErrorsubclass, you can throw these errors directly:import { ServerError } from "@apollo/client"; // instead of throwServerError(response, result, "error message"); // Use throw new ServerError("error message", { response, result }); -
#12304
86469a2Thanks @jerelmiller! - TheCache.DiffResult<T>type is now a union type with better type safety for both complete and partial results. Checkingdiff.completewill now narrow the type ofresultdepending on whether the value istrueorfalse.When
true,diff.resultwill be a non-null value equal to theTgeneric type. Whenfalse,diff.resultnow reportsresultasDeepPartial<T> | nullindicating that fields in the result may be missing (DeepPartial<T>) or empty entirely (null). -
#12396
00f3d0aThanks @jerelmiller! - Remove theerrorsproperty from the results emitted fromObservableQueryor returned fromclient.query. Read errors from theerrorproperty instead. -
#12367
e6af35eThanks @jerelmiller! - The result resolved from the promise returned from the execute function inuseLazyQueryis now anApolloQueryResulttype and no longer includes all the fields returned from theuseLazyQueryhook tuple.If you need access to the additional properties such as
called,refetch, etc. not included inApolloQueryResult, read them from the hook instead. -
#12367
e6af35eThanks @jerelmiller! -useLazyQuerywill no longer rerender with the loading state when calling the execute function the first time unless thenotifyOnNetworkStatusChangeoption is set totrue(which is the new default).If you prefer the behavior from 3.x, rerender the component with
notifyOnNetworkStatusChangeset tofalseafter the execute function is called the first time.function MyComponent() { const [notifyOnNetworkStatusChange, setNotifyOnNetworkStatusChange] = useState(true); const [execute] = useLazyQuery(query, { notifyOnNetworkStatusChange }); async function runExecute() { await execute(); // Set to false after the initial fetch to stop receiving notifications // about changes to the loading states. setNotifyOnNetworkStatusChange(false); } // ... } -
#12254
0028ac0Thanks @jerelmiller! - Changes the defaultAcceptheader toapplication/graphql-response+json. -
#12430
2ff66d0Thanks @jerelmiller! -ObservableQuery.setVariableswill now resolve with the last emitted result instead ofundefinedwhen either the variables match the current variables or there are no subscribers to the query. -
#12385
cad5117Thanks @phryneas! - Apollo Client now defaults to production mode, not development mode, if the environment cannot be determined.In modern bundlers, this should automatically be handled by the bundler loading the bundler with the
developmentexport condition.If neither the
productionnor thedevelopmentexport condition are used by the bundler/runtime, Apollo Client will fall back toglobalThis.__DEV__to determine if it should run in production or development mode.Unlike Apollo Client 3 though, if
globalThis.__DEV__is not set totrue, Apollo Client will now default toproduction, not todevelopment, behaviour.This switch to explicilty requiring
truealso resolves a situation where an HTML element withid="__DEV__"would create a global__DEV__variable with a referent to the DOM element, which in the past was picked up as "truthy" and would have triggered development mode. -
#12367
e6af35eThanks @jerelmiller! - Thereobserveoption is no longer available in the result returned fromuseLazyQuery. This was considered an internal API and should not be used directly. -
#12333
3e4beaaThanks @jerelmiller! - Fix type ofdataproperty onApolloQueryResult. Previously this field was non-optional, non-nullTData, however at runtime this value could be set toundefined. This field is now reported asTData | undefined.This will affect you in a handful of places:
- The
dataproperty emitted from the result passed to thenextcallback fromclient.watchQuery - Fetch-based APIs that return an
ApolloQueryResulttype such asobservableQuery.refetch,observableQuery.fetchMore, etc.
- The
-
#12367
e6af35eThanks @jerelmiller! - The promise returned when calling the execute function fromuseLazyQuerywill now reject when using anerrorPolicyofnonewhen GraphQL errors are returned from the result. -
#12223
69c1cb6Thanks @jerelmiller! - RemovesubscribeAndCounttesting utility from@apollo/client/testing. -
#12300
4d581e4Thanks @jerelmiller! - Moves all React-related exports to the@apollo/client/reactentrypoint and out of the main@apollo/cliententrypoint. This prevents the need to install React in order to use the core client.The following is a list of exports available in
@apollo/clientthat should now import from@apollo/client/react.ApolloConsumerApolloProvidercreateQueryPreloadergetApolloContextskipTokenuseApolloClientuseBackgroundQueryuseFragmentuseLazyQueryuseLoadableQueryuseMutationuseQueryuseQueryRefHandlersuseReactiveVaruseReadQueryuseSubscriptionuseSuspenseQuery
The following is a list of exports available in
@apollo/client/testingthat should now import from@apollo/client/testing/react:MockedProvider
-
#12428
abed922Thanks @jerelmiller! - Removes theurqlmultipart subscriptions utilities. Use the native multipart subscriptions support inurqlinstead. -
#12384
6aa6fd3Thanks @jerelmiller! - Switch to RxJS as the observable implementation.rxjsis now a peer dependency of Apollo Client which means you will now need to installrxjsin addition to@apollo/client.This change is mostly transparent, however transforming values on observables, common in link implementations, differs in RxJS vs
zen-observable. For example, you could modify values in the link chain emitted from a downstream link by using the.mapfunction. In RxJS, this is done with the.pipefunction and passing amapoperator instead.import { map } from "rxjs"; const link new ApolloLink((operation, forward) => { return forward(operation).pipe( map((result) => performTransform(result)) ); });For a full list of operators and comprehensive documentation on the capabilities of RxJS, check out the documentation.
-
#12329
61febe4Thanks @phryneas! - Rework package publish format (#12329, #12382)We have reworked the way Apollo Client is packaged.
- shipping ESM and CJS
- fixing up source maps
- the build targets a modern runtime environment (browserslist query:
"since 2023, node >= 20, not dead") - removed the "proxy directory"
package.jsonfiles, e.g.cache/core/package.jsonandreact/package.json. While these helped with older build tools, modern build tooling uses theexportsfield in the rootpackage.jsoninstead and the presence of these files can confuse modern build tooling. If your build tooling still relies on those, please update your imports to import from e.g.@apollo/client/cache/core/index.jsinstead of@apollo/client/cache/core- but generally, this should not be necessary. - added an
exportsfield topackage.jsonto expose entry points - instead of
globalThis.__DEV__, Apollo Client now primarily relies on thedevelopmentandproductionexports conditions. It falls back toglobalThis.__DEV__if the bundler doesn't know these, though.
-
#12397
2545a54Thanks @jerelmiller! - RemoveObservableQuery.resetQueryStoreErrorsmethod. This method reset some internal state that was not consumed elsewhere in the client and resulted in a no-op. -
#12384
6aa6fd3Thanks @jerelmiller! - RemovefromPromiseutility function. Usefrominstead. -
#12388
0d825beThanks @jerelmiller! - Require environments that supportWeakMap,WeakSetand symbols. Apollo Client would fallback toMapandSetif the weak versions were not available. This has been removed and expects that these features are available in the source environment.If you are running in an environment without
WeakMap,WeakSetor symbols, you will need to find appropriate polyfills. -
#12367
e6af35eThanks @jerelmiller! -useLazyQueryno longer supports calling the execute function in render and will now throw. If you need to execute the query immediately, useuseQueryinstead or move the call to auseEffect. -
#12367
e6af35eThanks @jerelmiller! - ThedefaultOptionsandinitialFetchPolicyoptions are no longer supported withuseLazyQuery.If you use
defaultOptions, pass those options directly to the hook instead. If you useinitialFetchPolicy, usefetchPolicyinstead. -
#12367
e6af35eThanks @jerelmiller! -useLazyQueryno longer supportsvariablesin the hook options and therefore no longer performs variable merging. The execute function must now be called withvariablesinstead.function MyComponent() { const [execute] = useLazyQuery(query); function runExecute() { execute({ variables: { ... }}); } }This change means the execute function returned from
useLazyQueryis more type-safe. The execute function will require you to pass avariablesoption if the query type includes required variables. -
#12304
86469a2Thanks @jerelmiller! - ### Changes for users ofInMemoryCachecache.diffnow returnsnullinstead of an empty object ({}) whenreturnPartialDataistrueand the result is empty.If you use
cache.diffdirectly withreturnPartialData: true, you will need to check fornullbefore accessing any other fields on theresultproperty. A non-null value indicates that at least one field was present in the cache for the given query document.Changes for third-party cache implementations
The client now expects
cache.diffto returnnullinstead of an empty object when there is no data that can be fulfilled from the cache andreturnPartialDataistrue. If your cache implementation returns an empty object, please update this to returnnull. -
#12430
2ff66d0Thanks @jerelmiller! - RemovesObservableQuery.result()method. If you use this method and need similar functionality, use thefirstValueFromhelper in RxJS.import { firstValueFrom, from } from "rxjs"; // The `from` is necessary to turn `observableQuery` into an RxJS observable const result = await firstValueFrom(from(observableQuery)); -
#12359
ebb4d96Thanks @jerelmiller! - Remove theonCompletedandonErrorcallbacks fromuseQueryanduseLazyQuery.See #12352 for more context on this change.
-
#12384
6aa6fd3Thanks @jerelmiller! - Subscriptions are no longer eagerly started after callingclient.subscribe. To kick off the subscription, you will now need to subscribe to the returned observable.// Subscriptions are no longer started when calling subscribe on its own. const subscriptionObservable = client.subscribe(...); // Instead, subscribe to the returned observable to kick off the subscription. subscriptionObservable.subscribe({ next: (value) => console.log(value) }); -
#12367
e6af35eThanks @jerelmiller! -useLazyQuerywill now only execute the query when the execute function is called. PreviouslyuseLazyQuerywould behave likeuseQueryafter the first call to the execute function which means changes to options might perform network requests.You can now safely rerender
useLazyQuerywith new options which will take effect the next time you manually trigger the query. -
#12384
6aa6fd3Thanks @jerelmiller! - RemovetoPromiseutility function. UsefirstValueFrominstead. -
#12304
86469a2Thanks @jerelmiller! - ### Changes for users ofInMemoryCachecache.diffno longer throws whenreturnPartialDatais set tofalsewithout a complete result. Instead,cache.diffwill returnnullwhen it is unable to read a full cache result.If you use
cache.diffdirectly withreturnPartialData: false, remove thetry/catchblock and replace with a check fornull.Changes for third-party cache implementations
The client now expects
cache.diffto returnnullinstead of throwing when the cache returns an incomplete result andreturnPartialDataisfalse. The internaltry/catchblocks have been removed aroundcache.diff. If your cache implementation throws for incomplete results, please update this to returnnull. -
#12211
c2736dbThanks @jerelmiller! - Remove the deprecatedQuery,Mutation, andSubscriptioncomponents. Use the provided React hooks instead.
Minor Changes
-
#12385
cad5117Thanks @phryneas! - Apollo Client is no longer usingts-invariant, but ships with a modified variant of it.The existing export
setLogVerbosityfrom@apollo/clientis still available and now points to this new integration. In most cases, you should be using this export. It will no longer adjust the verbosity ofts-invariantand as such no longer influence other packages relying onts-invariant.The new entry point
@apollo/client/utilities/invariantnow exportsinvariant,InvariantErrorandsetVerbosity. (Note that these tools are mostly meant to be used by Apollo Client and libraries directly based on Apollo Client like the@apollo/client-integration-*packages.) -
#12333
3e4beaaThanks @jerelmiller! - Deprecate thepartialflag onApolloQueryResultand make it a non-optional property. Previouslypartialwas only set conditionally if the result emitted was partial. This value is now available with all results that return anApolloQueryResult.
Patch Changes
-
#12291
ae5d06aThanks @phryneas! - Remove deprecatedresetApolloContextexport -
#12402
903c3efThanks @jerelmiller! - Use an an empty object ({}) rather than an object withnullprototype (Object.create(null)) in all areas that instantiate objects. -
#12385
cad5117Thanks @phryneas! - * dropped the deprecatedDEVexport from@apollo/client/utilitiesand@apollo/client/utilities/globals- moved the
__DEV__export from@apollo/client/utilities/globalsto@apollo/client/utilities/environment - moved the
invariant,newInvariantErrorandInvariantErrorexports from@apollo/client/utilities/globalsto@apollo/client/utilities/invariant
- moved the
-
#12432
c7c2f61Thanks @phryneas! - ObservableQuery: implement therxjsInteropObservableinterface to ensurefrom(observableQuery)stays possible -
#12385
cad5117Thanks @phryneas! -@apollo/client,@apollo/client/coreand@apollo/client/cacheno longer export an emptyCacheruntime object. This is meant to be a type-only namespace. -
#12384
6aa6fd3Thanks @jerelmiller! - Don't emit a partial cache result fromcache-onlyqueries whenreturnPartialDataisfalse.
-
- 3.14.112 Mar 2026
Release notes
Open source →Patch Changes
-
#13168
6b84ec0Thanks @jerelmiller! - Fix issue where muting a deprecation from one entrypoint would not mute the warning when checked in a different entrypoint. This caused some rogue deprecation warnings to appear in the console even though the warnings should have been muted. -
#12970
f91fab5Thanks @acemir! - Add a deprecation message for thevariableMatcheroption inMockLink. -
#13168
6b84ec0Thanks @jerelmiller! - Ensure deprecation warnings are properly silenced in React hooks when globally disabled.
-
- 3.14.021 Aug 2025
Release notes3 sources agree
Open source →Minor Changes
-
#12752
8b779b4Thanks @jerelmiller! - Add deprecations and warnings to remaining APIs changed in Apollo Client 4.0. -
#12746
0bcd2f4Thanks @jerelmiller! - Add warnings and deprecations for options and methods for all React APIs. -
#12751
567cad8Thanks @jerelmiller! - Add@deprecatedtags to all properties returned from any query API (e.g.client.query,observableQuery.refetch, etc.),client.mutate, andclient.subscribethat are no longer available in Apollo Client 4.0. -
#12746
0bcd2f4Thanks @jerelmiller! - AddpreloadQuery.toPromise(queryRef)as a replacement forqueryRef.toPromise().queryRef.toPromise()has been removed in Apollo Client 4.0 in favor ofpreloadQuery.toPromiseand is now considered deprecated. -
#12736
ea89440Thanks @jerelmiller! - Add deprecations and deprecation warnings forApolloClientoptions and methods. -
#12763
5de6a3dThanks @jerelmiller! - Version bump only to release latest asrc. -
#12459
1c5a031Thanks @jerelmiller! - ResetaddTypenameTransformandfragmentscaches when callingcache.gc()only whenresetResultCacheistrue. -
#12743
92ad409Thanks @jerelmiller! - Add deprecations and warnings foraddTypenameinInMemoryCacheandMockedProvider. -
#12743
92ad409Thanks @jerelmiller! - Add deprecations and warnings forcanonizeResults. -
#12751
567cad8Thanks @jerelmiller! - Warn when using astandbyfetch policy withclient.query.
Patch Changes
-
- 3.14.0-rc.07 Jul 2025pre-release
Release notes3 sources agree
Open source →Minor Changes
- #12763
5de6a3dThanks @jerelmiller! - Version bump only to release latest asrc.
- #12763
- 3.14.0-alpha.11 Jul 2025pre-release
Release notes3 sources agree
Open source →Minor Changes
-
#12752
8b779b4Thanks @jerelmiller! - Add deprecations and warnings to remaining APIs changed in Apollo Client 4.0. -
#12751
567cad8Thanks @jerelmiller! - Add@deprecatedtags to all properties returned from any query API (e.g.client.query,observableQuery.refetch, etc.),client.mutate, andclient.subscribethat are no longer available in Apollo Client 4.0. -
#12751
567cad8Thanks @jerelmiller! - Warn when using astandbyfetch policy withclient.query.
-
- 3.14.0-alpha.027 Jun 2025pre-release
Release notes3 sources agree
Open source →Minor Changes
-
#12746
0bcd2f4Thanks @jerelmiller! - Add warnings and deprecations for options and methods for all React APIs. -
#12746
0bcd2f4Thanks @jerelmiller! - AddpreloadQuery.toPromise(queryRef)as a replacement forqueryRef.toPromise().queryRef.toPromise()has been removed in Apollo Client 4.0 in favor ofpreloadQuery.toPromiseand is now considered deprecated. -
#12736
ea89440Thanks @jerelmiller! - Add deprecations and deprecation warnings forApolloClientoptions and methods. -
#12459
1c5a031Thanks @jerelmiller! - ResetaddTypenameTransformandfragmentscaches when callingcache.gc()only whenresetResultCacheistrue. -
#12743
92ad409Thanks @jerelmiller! - Add deprecations and warnings foraddTypenameinInMemoryCacheandMockedProvider. -
#12743
92ad409Thanks @jerelmiller! - Add deprecations and warnings forcanonizeResults.
Patch Changes
-
- 3.13.929 Jul 2025
- 3.13.9-rc.018 Jun 2025pre-releasewithdrawn: Version published by mistake
Nothing published for this version
- 3.13.817 Apr 2025
Release notes3 sources agree
Open source →Patch Changes
- #12567
c19d415Thanks @thearchitector! - Fix in-flight multipart urql subscription cancellation
- #12567
- 3.13.710 Apr 2025