From 2cc9e571c437e900681d6ffdd6f42035c23c48a9 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Fri, 26 Jul 2024 12:17:09 +0100
Subject: [PATCH 01/14] Nullability post
---
.prettierignore | 1 +
.../2024-08-14-exploring-true-nullability.mdx | 276 ++++++++++++++++++
2 files changed, 277 insertions(+)
create mode 100644 src/pages/blog/2024-08-14-exploring-true-nullability.mdx
diff --git a/.prettierignore b/.prettierignore
index 26cbea7e4e..07f4da3620 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,6 +7,7 @@ pnpm-lock.yaml
!src/pages/blog/2025-05-31-graphiql-4/index.mdx
!src/pages/blog/2025-06-10-graphiql-5/index.mdx
!src/pages/blog/2025-06-19-multioption-inputs-with-oneof/index.mdx
+!src/pages/blog/2026-08-14-true-nullability.mdx
*.jpg
scripts/**/*.json
diff --git a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
new file mode 100644
index 0000000000..aa7d7808ba
--- /dev/null
+++ b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
@@ -0,0 +1,276 @@
+---
+title: "Exploring 'True' Nullability in GraphQL"
+tags: ["spec"]
+date: 2024-08-14
+byline: Benjie Gillam
+---
+
+One of GraphQL's early decisions was to handle "partial failures"; this was a
+critical feature for Facebook - if one part of their backend infrastructure
+became degraded they wouldn't want to just render an error page, instead they
+wanted to serve the user a page with as much working data as they could.
+
+## Null propagation
+
+To accomplish this, if an error occured within a resolver, the resolver's value
+would be replaced with a `null`, and an error would be added to the `errors`
+array in the response. However, what if that field was marked as non-null? To
+solve that apparent contradiction, GraphQL introduced the "error propagation"
+behavior (also known colloquially as "null bubbling") - when a `null` (from an
+error or otherwise) occurs in a non-nullable position, the parent position
+(either a field or a list item) is made `null` and this behavior would repeat if
+the parent position was also non-nullable.
+
+This solved the issue, and meant that GraphQL's nullability promises were still
+honoured; but it wasn't without complications.
+
+### Complication 1: partial failures
+
+We want to be resilient to systems failing; but errors that occur in
+non-nullable positions cascade to surrounding parts of the query, making less
+and less data available to be rendered. This seems contrary to our "partial
+failures" aim, but it's easy to solve - we just make sure that the positions
+where we expect errors to occur are nullable so that errors don't propagate
+further. Clients now needed to ensure they handle any nulls that occur in these
+positions; but that seemed like a fair trade.
+
+### Complication 2: nullable epidemic
+
+But, it turns out, almost any field in your GraphQL schema could raise an error
+
+- errors might not only be caused by backend services becoming unavailable or
+ responding in unexpected ways; they can also be caused by simple programming
+ errors in your business logic, data consistency errors (e.g. expecting a
+ boolean but receiving a float), or any other cause.
+
+Since we don't want to "blow up" the entire response if any such issue occurred,
+we've moved to strongly encourage nullable usage throughout a schema, only
+adding the non-nullable `!` marker to positions where we're truly sure that
+field is extremely unlikely to error. This has the effect of meaning that
+developers consuming the GraphQL API have to handle null in more positions than
+they would expect, giving them a harder time.
+
+### Complication 3: normalized caching
+
+Many modern GraphQL clients use a "normalized" cache, such that updates pulled
+down from the API in one query can automatically update all the previously
+rendered data across the application. This helps ensure consistency for users,
+and is a powerful feature.
+
+But if an error occurs in a non-nullable position, it's
+[no longer safe](https://github.com/graphql/nullability-wg/issues/20) to store
+the data to the normalized cache.
+
+## The Nullability Working Group
+
+At first, we thought the solution to this was to give clients control over the
+nullability of a response, so we set up the Client-Controlled Nullability (CCN)
+Working Group. Later, we renamed the working group to the Nullability WG to show
+that it encompassed all potential solutions to this problem.
+
+### Client-controlled nullability
+
+The first CCN WG proposal was that we could adorn the queries we issue to the
+server with sigils indicating our desired nullability overrides for the given
+fields - a `?` would be added to fields where we don't mind if they're null, but
+we definitely want errors to stop there; and add a `!` to fields where we
+definitely don't want a null to occur. This would give consumers control over
+where errors/nulls were handled; but after much exploration of the topic over
+years we found numerous issues that traded one set of concerns for another.
+
+We needed a better solution.
+
+### True nullability schema
+
+Jordan Eldredge
+[proposed](https://github.com/graphql/nullability-wg/discussions/22) that making
+fields nullable to handle error propagation was hiding the "true" nullability of
+the data. Instead, he suggested, we should have the schema represent the true
+nullability, and put the responsibility on clients to use the `?` CCN operator
+to handle errors in the relevant places.
+
+However, this would mean that clients such as Relay would want to add `?` in
+every position, causing an "explosion" of question marks, because really what
+Relay desired was to disable null propagation entirely.
+
+### A new type
+
+Getting the relevant experts together at GraphQLConf 2023 re-energized the
+discussions and sparked new ideas. After seeing Stephen Spalding's "Nullability
+Sandwich" talk and chatting with Jordan, Stephen and others in amongst the
+seating, Benjie had an idea that felt right to him. He grabbed his laptop and
+sat quietly for an hour at one of the tables in the sponsors room and wrote up
+[the spec edits](https://github.com/graphql/graphql-spec/pull/1046) to represent
+a "null only on error" type. This type would allow us to express the "true"
+nullability of a field whilst also indicating that errors may happen that should
+be handled, but would not "blow up" the response.
+
+To maintain backwards compatibility, clients would need to opt in to seeing this
+new type (otherwise it would masquerade as nullable); and it would be their
+choice of how to handle the nullability of this position, knowing that the data
+would only contain a `null` there if a matching error existed in the `errors`
+list.
+
+A
+[number of alternative syntaxes](https://gist.github.com/benjie/19d784721d1658b89fd8954e7ee07034)
+were suggested for this, but none were well liked.
+
+### A new approach to client error handling
+
+Also around the time of GraphQLConf 2023 the Relay team shared
+[a presentation](https://docs.google.com/presentation/u/2/d/1rfWeBcyJkiNqyxPxUIKxgbExmfdjA70t/edit?pli=1#slide=id.p8)
+on some of the things they were thinking around errors. In particular they
+discussed the `@catch` directive which would give users control over how errors
+were represented in the data being rendered, allowing the client to
+differentiate an error from a legitimate null. Over the coming months, many
+behaviors were discussed at the Nullability WG; one particularly compelling one
+was that clients could throw the error when an errored field was read, and rely
+on framework mechanics (such as React's
+[error boundaries](https://legacy.reactjs.org/docs/error-boundaries.html)) to
+handle them.
+
+### A new mode
+
+Lee [proposed](https://github.com/graphql/graphql-wg/discussions/1410) that we
+introduce a schema directive, `@strictNullability`, whereby we would change what
+the syntax meant - `Int?` for nullable, `Int` for null-only-on-error, and `Int!`
+for never-null. This proposal was well liked, but wasn't a clear win, it
+introduced many complexities, not least migration costs.
+
+### A pivotal discussion
+
+Lee and Benjie had a call where they discussed all of this in depth, including
+their two respective solutions, their pros and cons. It was clear that neither
+solution was quite there, but we were getting closer and closer to a solution.
+This long and detailed highly technical discussion inspired Benjie to write up
+[a new proposal](https://github.com/graphql/nullability-wg/discussions/58),
+which has been iterated further, and we aim to describe below.
+
+## Our latest proposal
+
+We're now proposing a new opt-in mode to solve the nullability problem. It's
+important to note that clients and servers that don't opt-in will be completely
+unaffected by this change (and a client may opt-in without a server opting-in,
+and vice-versa, without causing any issues - in these cases, traditional mode
+will be used).
+
+### No-error-propogation mode
+
+The new proposal centers around the premise of allowing clients to disable the
+"error propagation" behavior discussed above.
+
+Clients that opt-in to this behavior take responsibility for interpretting the
+response as a whole, correlating the `data` and `errors` properties of the
+response. With error propagation disabled and the fact that any field could
+potentially throw an error, all positions in `data` can potentially contain a
+`null` value. Clients in this mode must cross-check any `null` values against
+`errors` to determine if it's a true null, or an error.
+
+### "Smart" clients
+
+The no-error-propagation mode is intended for use by "smart" clients such as
+Relay, Apollo Client, URQL and others which understand GraphQL deeply and are
+responsible for the storage and retrieval of fetched GraphQL data. These clients
+are well positioned to handle the responsibilities outlined above.
+
+By disabling error propagation, these clients will be able to safely update
+their stores (including normalized stores) even when errors occur. They can also
+re-implement traditional GraphQL error propagation on top of these new
+foundations, shielding applications developers from needing to learn this new
+behavior (whilst still allowing them to reap the benefits!). They can even take
+on advanced behaviors, such as throwing the error when the application developer
+attempts to read from an errored field, allowing the developer to handle errors
+with their own more natural error boundaries.
+
+### True nullability
+
+Just like in traditional mode, for clients operating in no-error-propagation
+mode fields are either nullable or non-nullable. However; unlike in traditional
+mode, no-error-propagation mode allows for errors to be represented in any
+position:
+
+- nullable (e.g. `Int`): a value, an error, or a true `null`;
+- non-nullable (e.g. `Int!`): a value **or an error**.
+
+_(In traditional mode, non-nullable fields cannot represent an error because the
+error propagates to the nearest nullable position.)_
+
+Since this mode allows every field, whether nullable or non-nullable, to
+represent an error, the schema can safely indicate to clients in this mode the
+true intended nullability of a field. If the schema designer knows that a field
+should never be null unless an error occurs, they would mark the field as
+non-nullable (but only for clients in no-null-propagation mode; see "schema
+developers" below).
+
+### Client reflection of true nullability
+
+Smart clients can ask the schema about the "true" nullability of each field via
+introspection, and can generate a derived SDL by combining that information with
+their knowledge of how the client handles errors. This derived SDL would look
+like the traditional representation of the schema, but with more fields
+represented as non-nullable where the true nullability of the underlying schema
+is reflected. Application developers would issue queries and mutations in the
+same way they always had, but now their generated types don't need to handle
+`null` in as many positions as before, increasing developer happiness.
+
+### Schema developers
+
+Schemas that wish to add support for indicating the "true nullability" of a
+field in no-error-propagation mode need to be able to discern which types show
+up as non-nullable in both modes (traditional non-null types), and which types
+show up as non-nullable only in no-error-propagation mode. For this later
+concern we've introduced the concept, of a "semantic" non-null type:
+
+- "strict" (traditional) non-nullable - shows up as non-nullable in both
+ traditional mode and no-null-propagation mode
+- "semantic" non-nullable, aka "null only on error" - shows up as non-nullable
+ only in no-null-propagation mode; in traditional mode it will masquerade as
+ nullable
+
+Only clients that opt-in to seeing the true nullability will see this
+difference, otherwise the nullability of the chosen mode (traditional or
+no-error-propagation) will be reflected by introspection.
+
+### Representation in SDL
+
+Application developers will only need to deal with traditional SDL that
+represents traditional nullability concerns. If these developers are using
+"smart" clients then they should get this SDL from the client rather than from
+the server, this allows them to see the nullability that the client guarantees
+based on how it will handle the "true" nullability of the schema, how it handles
+errors, and factoring in any local schema extensions that may have been added.
+
+Client-derived SDL (see "client reflection of true nullability" above) can be
+used for concerns such as code generation, which will work in the traditional
+way with no need for changes (but happier developers since there will be fewer
+nullable positions!).
+
+However, schema developers and people working on "smart" clients may need to
+represent the differences between "strict" and "semantic" non-nullable in SDL.
+For these people, we're introducing the `@extendedNullability` document
+directive. When this directive is present at the top of a document, the `!`
+symbol means that a type will appear as non-nullable only in no-null-propagation
+mode, and a new `!!` symbol will represent that a type will appear as
+non-nullable in both traditional and no-error-propagation mode.
+
+| Traditional Mode | No-null-propagation mode | Example |
+| ---------------- | ------------------------ | ------- |
+| Nullable | Nullable | `Int` |
+| Nullable | Non-nullable | `Int!` |
+| Non-nullable\* | Non-nullable | `Int!!` |
+
+The `!!` symbol is designed to look a little scary - it should be used with
+caution (like `!` in traditional schemas) because it is the symbol that means
+that errors will propagate in traditional mode, "blowing up" parent selection
+sets.
+
+## Get involved
+
+Like all GraphQL Working Groups, the Nullability Working Group is open to all.
+Whether you work on a GraphQL client or are just a GraphQL user with thoughts on
+nullability, we want to hear from you - add yourself to an
+[upcoming working group](https://github.com/graphql/nullability-wg/) or chat
+with us in the #nullability-wg channel in
+[the GraphQL Discord](https://discord.graphql.org). This solution is not yet
+merged into the specification, so there's still time for iteration and
+alternative ideas!
From ce95a586621e2deed1f68372e7ede8162c586020 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Thu, 15 Aug 2024 12:04:46 +0100
Subject: [PATCH 02/14] Some edits
---
.../2024-08-14-exploring-true-nullability.mdx | 180 +++++++++---------
1 file changed, 94 insertions(+), 86 deletions(-)
diff --git a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
index aa7d7808ba..d8a1c7d1c1 100644
--- a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
+++ b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
@@ -5,7 +5,7 @@ date: 2024-08-14
byline: Benjie Gillam
---
-One of GraphQL's early decisions was to handle "partial failures"; this was a
+One of GraphQL's early decisions was to allow "partial success"; this was a
critical feature for Facebook - if one part of their backend infrastructure
became degraded they wouldn't want to just render an error page, instead they
wanted to serve the user a page with as much working data as they could.
@@ -18,37 +18,38 @@ array in the response. However, what if that field was marked as non-null? To
solve that apparent contradiction, GraphQL introduced the "error propagation"
behavior (also known colloquially as "null bubbling") - when a `null` (from an
error or otherwise) occurs in a non-nullable position, the parent position
-(either a field or a list item) is made `null` and this behavior would repeat if
-the parent position was also non-nullable.
+(either a field or a list item) is made `null` instead. This behavior would
+repeat if the parent position was also non-nullable, and this could cascade (or
+"bubble") all the way up to the root of the query if everything in the path is
+non-nullable.
This solved the issue, and meant that GraphQL's nullability promises were still
honoured; but it wasn't without complications.
-### Complication 1: partial failures
+### Complication 1: partial success
We want to be resilient to systems failing; but errors that occur in
non-nullable positions cascade to surrounding parts of the query, making less
and less data available to be rendered. This seems contrary to our "partial
-failures" aim, but it's easy to solve - we just make sure that the positions
+success" aim, but it's easy to solve - we just make sure that the positions
where we expect errors to occur are nullable so that errors don't propagate
further. Clients now needed to ensure they handle any nulls that occur in these
positions; but that seemed like a fair trade.
### Complication 2: nullable epidemic
-But, it turns out, almost any field in your GraphQL schema could raise an error
-
-- errors might not only be caused by backend services becoming unavailable or
- responding in unexpected ways; they can also be caused by simple programming
- errors in your business logic, data consistency errors (e.g. expecting a
- boolean but receiving a float), or any other cause.
+Almost any field in your GraphQL schema could raise an error - errors might not
+only be caused by backend services becoming unavailable or responding in
+unexpected ways; they can also be caused by simple programming errors in your
+business logic, data consistency errors (e.g. expecting a boolean but receiving
+a float), or any other cause.
Since we don't want to "blow up" the entire response if any such issue occurred,
we've moved to strongly encourage nullable usage throughout a schema, only
adding the non-nullable `!` marker to positions where we're truly sure that
field is extremely unlikely to error. This has the effect of meaning that
-developers consuming the GraphQL API have to handle null in more positions than
-they would expect, giving them a harder time.
+developers consuming the GraphQL API have to handle potential nulls in more
+positions than they would expect, making for additional work.
### Complication 3: normalized caching
@@ -57,7 +58,7 @@ down from the API in one query can automatically update all the previously
rendered data across the application. This helps ensure consistency for users,
and is a powerful feature.
-But if an error occurs in a non-nullable position, it's
+However, if an error occurs in a non-nullable position, it's
[no longer safe](https://github.com/graphql/nullability-wg/issues/20) to store
the data to the normalized cache.
@@ -70,15 +71,20 @@ that it encompassed all potential solutions to this problem.
### Client-controlled nullability
-The first CCN WG proposal was that we could adorn the queries we issue to the
-server with sigils indicating our desired nullability overrides for the given
-fields - a `?` would be added to fields where we don't mind if they're null, but
-we definitely want errors to stop there; and add a `!` to fields where we
-definitely don't want a null to occur. This would give consumers control over
-where errors/nulls were handled; but after much exploration of the topic over
-years we found numerous issues that traded one set of concerns for another.
+The first Nullability WG proposal came from a collaboration between Yelp and
+Netflix, with contributions from GraphQL WG regulars Alex Reilly, Mark Larah,
+and Stephen Spalding among others. They proposed we could adorn the queries we
+issue to the server with sigils indicating our desired nullability overrides for
+the given fields - client-controlled nullability.
+
+A `?` would be added to fields where we don't mind if they're null, but we
+definitely want errors to stop there; and add a `!` to fields where we
+definitely don't want a null to occur (whether or not there is an error). This
+would give consumers control over where errors/nulls were handled.
-We needed a better solution.
+However, after much exploration of the topic over years we found numerous issues
+that traded one set of concerns for another. We kept iterating whilst we looked
+for a solution to these tradeoffs.
### True nullability schema
@@ -96,24 +102,22 @@ Relay desired was to disable null propagation entirely.
### A new type
Getting the relevant experts together at GraphQLConf 2023 re-energized the
-discussions and sparked new ideas. After seeing Stephen Spalding's "Nullability
-Sandwich" talk and chatting with Jordan, Stephen and others in amongst the
-seating, Benjie had an idea that felt right to him. He grabbed his laptop and
-sat quietly for an hour at one of the tables in the sponsors room and wrote up
-[the spec edits](https://github.com/graphql/graphql-spec/pull/1046) to represent
-a "null only on error" type. This type would allow us to express the "true"
+discussions and sparked new ideas. After seeing Stephen's "Nullability Sandwich"
+talk and chatting with Jordan, Stephen and others in the corridor, Benjie Gillam
+was inspired to [propose](https://github.com/graphql/graphql-spec/pull/1046) a
+"null only on error" type. This type would allow us to express the "true"
nullability of a field whilst also indicating that errors may happen that should
be handled, but would not "blow up" the response.
To maintain backwards compatibility, clients would need to opt in to seeing this
-new type (otherwise it would masquerade as nullable); and it would be their
-choice of how to handle the nullability of this position, knowing that the data
-would only contain a `null` there if a matching error existed in the `errors`
-list.
+new type (otherwise it would masquerade as nullable). It would be up to the
+client how to handle the nullability of this position knowing that a "null only
+on error" position would only contain a `null` if a matching error existed in
+the `errors` list.
A
[number of alternative syntaxes](https://gist.github.com/benjie/19d784721d1658b89fd8954e7ee07034)
-were suggested for this, but none were well liked.
+were suggested for this new type, but none were well liked.
### A new approach to client error handling
@@ -129,30 +133,32 @@ on framework mechanics (such as React's
[error boundaries](https://legacy.reactjs.org/docs/error-boundaries.html)) to
handle them.
-### A new mode
+### Strict semantic nullability
-Lee [proposed](https://github.com/graphql/graphql-wg/discussions/1410) that we
+GraphQL Foundation director Lee Byron
+[proposed](https://github.com/graphql/graphql-wg/discussions/1410) that we
introduce a schema directive, `@strictNullability`, whereby we would change what
the syntax meant - `Int?` for nullable, `Int` for null-only-on-error, and `Int!`
-for never-null. This proposal was well liked, but wasn't a clear win, it
-introduced many complexities, not least migration costs.
+for never-null. This proposal was well liked, but wasn't a clear win; it
+introduced many complexities including migration costs and concerns over schema
+evolution.
### A pivotal discussion
-Lee and Benjie had a call where they discussed all of this in depth, including
-their two respective solutions, their pros and cons. It was clear that neither
-solution was quite there, but we were getting closer and closer to a solution.
-This long and detailed highly technical discussion inspired Benjie to write up
+Lee and Benjie had a call where they discussed the history of GraphQL
+nullability and all the relevant proposals in depth, including their two
+respective solutions. It was clear that though no solution was quite there, the
+solutions converging hinted we were getting closer and closer to an answer. This
+long and detailed highly technical discussion inspired
[a new proposal](https://github.com/graphql/nullability-wg/discussions/58),
which has been iterated further, and we aim to describe below.
## Our latest proposal
-We're now proposing a new opt-in mode to solve the nullability problem. It's
-important to note that clients and servers that don't opt-in will be completely
-unaffected by this change (and a client may opt-in without a server opting-in,
-and vice-versa, without causing any issues - in these cases, traditional mode
-will be used).
+We're now proposing a new opt-in execution mode to solve the nullability
+problem. It's important to note that both the client and the server must opt-in
+to this new mode for it to take effect, otherwise the traditional execution mode
+will be used.
### No-error-propogation mode
@@ -161,10 +167,11 @@ The new proposal centers around the premise of allowing clients to disable the
Clients that opt-in to this behavior take responsibility for interpretting the
response as a whole, correlating the `data` and `errors` properties of the
-response. With error propagation disabled and the fact that any field could
-potentially throw an error, all positions in `data` can potentially contain a
-`null` value. Clients in this mode must cross-check any `null` values against
-`errors` to determine if it's a true null, or an error.
+response. With error propagation disabled and the previously discussed fact that
+any field could potentially throw an error, all positions in `data` can
+potentially contain a `null` value. Clients in this mode must cross-check any
+`null` values against `errors` to determine if it represents a true `null`, or
+an error.
### "Smart" clients
@@ -180,7 +187,7 @@ foundations, shielding applications developers from needing to learn this new
behavior (whilst still allowing them to reap the benefits!). They can even take
on advanced behaviors, such as throwing the error when the application developer
attempts to read from an errored field, allowing the developer to handle errors
-with their own more natural error boundaries.
+with their system's native error boundaries.
### True nullability
@@ -190,7 +197,7 @@ mode, no-error-propagation mode allows for errors to be represented in any
position:
- nullable (e.g. `Int`): a value, an error, or a true `null`;
-- non-nullable (e.g. `Int!`): a value **or an error**.
+- non-nullable (e.g. `Int!`): a value, **or an error**.
_(In traditional mode, non-nullable fields cannot represent an error because the
error propagates to the nearest nullable position.)_
@@ -198,20 +205,21 @@ error propagates to the nearest nullable position.)_
Since this mode allows every field, whether nullable or non-nullable, to
represent an error, the schema can safely indicate to clients in this mode the
true intended nullability of a field. If the schema designer knows that a field
-should never be null unless an error occurs, they would mark the field as
-non-nullable (but only for clients in no-null-propagation mode; see "schema
-developers" below).
+should never be null unless an error occurs, they can mark the field as
+"non-nullable for clients in no-error-propagation mode" (see "schema developers"
+below).
### Client reflection of true nullability
Smart clients can ask the schema about the "true" nullability of each field via
introspection, and can generate a derived SDL by combining that information with
-their knowledge of how the client handles errors. This derived SDL would look
-like the traditional representation of the schema, but with more fields
-represented as non-nullable where the true nullability of the underlying schema
-is reflected. Application developers would issue queries and mutations in the
-same way they always had, but now their generated types don't need to handle
-`null` in as many positions as before, increasing developer happiness.
+their knowledge of how the client handles errors. This derived SDL, dependent on
+client behavior, would look like the traditional representation of the schema,
+but with more fields potentially marked as non-nullable where the true
+nullability of the underlying schema has been reflected. Application developers
+would issue queries and mutations in the same way they always had, but now their
+generated types may not need to handle `null` in as many positions as before,
+increasing developer happiness.
### Schema developers
@@ -224,40 +232,40 @@ concern we've introduced the concept, of a "semantic" non-null type:
- "strict" (traditional) non-nullable - shows up as non-nullable in both
traditional mode and no-null-propagation mode
- "semantic" non-nullable, aka "null only on error" - shows up as non-nullable
- only in no-null-propagation mode; in traditional mode it will masquerade as
- nullable
+ in no-null-propagation mode and masquerades as nullable in traditional mode
-Only clients that opt-in to seeing the true nullability will see this
-difference, otherwise the nullability of the chosen mode (traditional or
-no-error-propagation) will be reflected by introspection.
+Only clients that opt-in to seeing the "true" nullability will see these two
+different types of nullability, otherwise the nullability of the chosen mode
+(traditional or no-error-propagation) will be reflected by introspection.
### Representation in SDL
Application developers will only need to deal with traditional SDL that
represents traditional nullability concerns. If these developers are using
-"smart" clients then they should get this SDL from the client rather than from
-the server, this allows them to see the nullability that the client guarantees
-based on how it will handle the "true" nullability of the schema, how it handles
-errors, and factoring in any local schema extensions that may have been added.
+"smart" clients then they should source this SDL from the client rather than
+from the server, this allows them to see the nullability that the client
+guarantees based on how it will handle the "true" nullability of the schema, how
+it handles errors, and factoring in any local schema extensions that may have
+been added.
Client-derived SDL (see "client reflection of true nullability" above) can be
used for concerns such as code generation, which will work in the traditional
-way with no need for changes (but happier developers since there will be fewer
-nullable positions!).
-
-However, schema developers and people working on "smart" clients may need to
-represent the differences between "strict" and "semantic" non-nullable in SDL.
-For these people, we're introducing the `@extendedNullability` document
-directive. When this directive is present at the top of a document, the `!`
-symbol means that a type will appear as non-nullable only in no-null-propagation
-mode, and a new `!!` symbol will represent that a type will appear as
-non-nullable in both traditional and no-error-propagation mode.
-
-| Traditional Mode | No-null-propagation mode | Example |
-| ---------------- | ------------------------ | ------- |
-| Nullable | Nullable | `Int` |
-| Nullable | Non-nullable | `Int!` |
-| Non-nullable\* | Non-nullable | `Int!!` |
+way with no need for changes (but happier developers if there are fewer nullable
+positions!).
+
+Schema developers and people working on "smart" clients may need to represent
+the differences between "strict" and "semantic" non-nullable in SDL. For these
+people, we're introducing the `@extendedNullability` document directive. When
+this directive is present at the top of a document, the `!` symbol means that a
+type will appear as non-nullable only in no-error-propagation mode, and a new
+`!!` symbol will represent that a type will appear as non-nullable in both
+traditional and no-error-propagation mode.
+
+| Traditional Mode | No-error-propagation mode | Example |
+| ---------------- | ------------------------- | ------- |
+| Nullable | Nullable | `Int` |
+| Nullable | Non-nullable | `Int!` |
+| Non-nullable\* | Non-nullable | `Int!!` |
The `!!` symbol is designed to look a little scary - it should be used with
caution (like `!` in traditional schemas) because it is the symbol that means
From 6c84ac34322c32d8ae7bebca0bc4cbb4af23de74 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Thu, 22 Aug 2024 09:57:36 +0100
Subject: [PATCH 03/14] Fix typo
---
src/pages/blog/2024-08-14-exploring-true-nullability.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
index d8a1c7d1c1..97c6ce6246 100644
--- a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
+++ b/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
@@ -160,7 +160,7 @@ problem. It's important to note that both the client and the server must opt-in
to this new mode for it to take effect, otherwise the traditional execution mode
will be used.
-### No-error-propogation mode
+### No-error-propagation mode
The new proposal centers around the premise of allowing clients to disable the
"error propagation" behavior discussed above.
From f43aa45a98714c6d99aa0dd46f2f7d17fbdaee4d Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Fri, 24 Jul 2026 12:25:15 +0100
Subject: [PATCH 04/14] Rename and edit
---
...ty.mdx => 2026-08-14-true-nullability.mdx} | 104 ++++++++++++++----
1 file changed, 80 insertions(+), 24 deletions(-)
rename src/pages/blog/{2024-08-14-exploring-true-nullability.mdx => 2026-08-14-true-nullability.mdx} (79%)
diff --git a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
similarity index 79%
rename from src/pages/blog/2024-08-14-exploring-true-nullability.mdx
rename to src/pages/blog/2026-08-14-true-nullability.mdx
index 97c6ce6246..ba117be9f0 100644
--- a/src/pages/blog/2024-08-14-exploring-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -1,7 +1,7 @@
---
-title: "Exploring 'True' Nullability in GraphQL"
+title: "'True' Nullability is coming to GraphQL"
tags: ["spec"]
-date: 2024-08-14
+date: 2026-09-10
byline: Benjie Gillam
---
@@ -14,42 +14,51 @@ wanted to serve the user a page with as much working data as they could.
To accomplish this, if an error occured within a resolver, the resolver's value
would be replaced with a `null`, and an error would be added to the `errors`
-array in the response. However, what if that field was marked as non-null? To
-solve that apparent contradiction, GraphQL introduced the "error propagation"
-behavior (also known colloquially as "null bubbling") - when a `null` (from an
-error or otherwise) occurs in a non-nullable position, the parent position
-(either a field or a list item) is made `null` instead. This behavior would
-repeat if the parent position was also non-nullable, and this could cascade (or
-"bubble") all the way up to the root of the query if everything in the path is
-non-nullable.
+array in the response.
+
+But what if that field was marked as non-null?
+
+To solve that apparent contradiction, GraphQL introduced "error propagation"
+(aka "null bubbling"): when a `null` occurs in a non-nullable position, the
+parent position is made `null` instead. If that position is also non-nullable,
+it's parent will be made `null` instead, and so on up the tree until a nullable
+position is made `null`.
This solved the issue, and meant that GraphQL's nullability promises were still
-honoured; but it wasn't without complications.
+honoured; but it wasn't without complications...
### Complication 1: partial success
We want to be resilient to systems failing; but errors that occur in
non-nullable positions cascade to surrounding parts of the query, making less
-and less data available to be rendered. This seems contrary to our "partial
-success" aim, but it's easy to solve - we just make sure that the positions
-where we expect errors to occur are nullable so that errors don't propagate
-further. Clients now needed to ensure they handle any nulls that occur in these
-positions; but that seemed like a fair trade.
+and less data available to be rendered.
+
+This seems contrary to our "partial success" aim, but it's easy to solve - we
+just make sure that the positions where we expect errors to occur are nullable
+so that errors don't propagate further.
+
+Unfortunately this means clients now need `null`-handling code in a few more
+places, but what is engineering if not choosing trade-offs...
### Complication 2: nullable epidemic
+So... where are errors likely to occur?
+
Almost any field in your GraphQL schema could raise an error - errors might not
only be caused by backend services becoming unavailable or responding in
unexpected ways; they can also be caused by simple programming errors in your
business logic, data consistency errors (e.g. expecting a boolean but receiving
-a float), or any other cause.
+a float), access controls, or any other cause.
Since we don't want to "blow up" the entire response if any such issue occurred,
we've moved to strongly encourage nullable usage throughout a schema, only
adding the non-nullable `!` marker to positions where we're truly sure that
-field is extremely unlikely to error. This has the effect of meaning that
-developers consuming the GraphQL API have to handle potential nulls in more
-positions than they would expect, making for additional work.
+field is extremely unlikely to error.
+
+This "nullable by default" has the effect of meaning that developers consuming
+the GraphQL API have to handle potential nulls in more positions than they would
+expect, an explosion of null checks leading people to even call into question
+the value of GraphQL's "type safety".
### Complication 3: normalized caching
@@ -65,9 +74,10 @@ the data to the normalized cache.
## The Nullability Working Group
At first, we thought the solution to this was to give clients control over the
-nullability of a response, so we set up the Client-Controlled Nullability (CCN)
-Working Group. Later, we renamed the working group to the Nullability WG to show
-that it encompassed all potential solutions to this problem.
+nullability of each field in response, so we set up the Client-Controlled
+Nullability (CCN) Working Group. Later, we renamed the working group to the
+Nullability WG to show that it encompassed all potential solutions to this
+problem.
### Client-controlled nullability
@@ -153,7 +163,53 @@ long and detailed highly technical discussion inspired
[a new proposal](https://github.com/graphql/nullability-wg/discussions/58),
which has been iterated further, and we aim to describe below.
-## Our latest proposal
+## Introducing the `onError` request property
+
+The new `onError` request property allows a client to indicate its preference as
+to how errors are handled by the GraphQL service (each option detailed below).
+For example:
+
+```diff
+ POST /graphql HTTP/1.1
+ Host: example.com
+ Content-Type: application/json
+ Accept: application/graphql-response+json
+
+ {
+ "query": "query UserProfile($id: ID!) { user(id: $id) { id name avatarUrl bestFriend { name } } }",
++ "onError": "NULL",
+ "variables": { "id": "27" }
+ }
+```
+
+Services that support `onError` must honor the specified behavior. If a service
+does not support `onError` then the request property will be ignored, resulting
+in behavior equivalent to `onError: PROPAGATE`. Services will soon be able to
+indicate their support for the `onError` request property through [service
+capabilities](https://github.com/graphql/graphql-spec/pull/1208)), allowing
+clients to auto-discover and depend upon the capability.
+
+### `onError: PROPAGATE`
+
+This is the traditional error propagation behavior that we all know and... love?
+
+Setting `onError: PROPAGATE` will be equivalent to the error behavior in the
+initial 2015 GraphQL Specification release: error propagation/null bubbling.
+
+### `onError: ABORT`
+
+Ad-hoc scripts and similar clients throw away entire responses if any error
+occurs, but currently the server still computes the "partial success" response
+anyway. This mode allows clients to indicate that if any error occurs, they
+won't be reading the data - any error should result in a
+`{ data: null, errors: [...] }` response - and thus the service can abort
+execution when the first error occurs.
+
+### `onError: NULL`
+
+This is what we're excited about!
+
+...
We're now proposing a new opt-in execution mode to solve the nullability
problem. It's important to note that both the client and the server must opt-in
From 19c271de08e9b0e7bd08d5054d5e9025fc94c923 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 10:39:31 +0100
Subject: [PATCH 05/14] Updating onError
---
.../blog/2026-08-14-true-nullability.mdx | 70 ++++++++++++-------
1 file changed, 44 insertions(+), 26 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index ba117be9f0..716b4f4d13 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -158,10 +158,24 @@ evolution.
Lee and Benjie had a call where they discussed the history of GraphQL
nullability and all the relevant proposals in depth, including their two
respective solutions. It was clear that though no solution was quite there, the
-solutions converging hinted we were getting closer and closer to an answer. This
-long and detailed highly technical discussion inspired
-[a new proposal](https://github.com/graphql/nullability-wg/discussions/58),
-which has been iterated further, and we aim to describe below.
+solutions converging hinted we were getting closer and closer to an answer.
+
+This long and detailed highly technical discussion ultimately led to the
+realisation that error propagation itself was the issue. Rather than
+working around error propagation with new "null only on error" types, what we
+really needed was a way for "smart clients" to turn off error propagation
+entirely.
+
+### `@experimental_disableErrorPropagation`
+
+Our first punt at this was the `@experimental_disableErrorPropagation` directive
+that could be added to operations to disable error propagation. However, we
+quickly realised that this was cumbersome, inconsistent, and also became the
+responsibility of the developer rather than the client. A smart client that
+understands the schema should be able to fully re-implement traditional error
+propagation locally: `data` and `errors` contains all the information it would
+need to do so. And if a client supports this, it would want to disable error
+propagation for every request...
## Introducing the `onError` request property
@@ -184,19 +198,21 @@ For example:
Services that support `onError` must honor the specified behavior. If a service
does not support `onError` then the request property will be ignored, resulting
-in behavior equivalent to `onError: PROPAGATE`. Services will soon be able to
-indicate their support for the `onError` request property through [service
+in the traditional behavior - equivalent to `onError: PROPAGATE`.
+
+Services will soon be able to indicate their support for the `onError` request
+property through [service
capabilities](https://github.com/graphql/graphql-spec/pull/1208)), allowing
clients to auto-discover and depend upon the capability.
-### `onError: PROPAGATE`
+### `onError: "PROPAGATE"`
-This is the traditional error propagation behavior that we all know and... love?
+This is the traditional error propagation behavior that we all know and... "love"?
Setting `onError: PROPAGATE` will be equivalent to the error behavior in the
-initial 2015 GraphQL Specification release: error propagation/null bubbling.
+initial 2015 GraphQL Specification: error propagation/null bubbling.
-### `onError: ABORT`
+### `onError: "ABORT"`
Ad-hoc scripts and similar clients throw away entire responses if any error
occurs, but currently the server still computes the "partial success" response
@@ -205,29 +221,31 @@ won't be reading the data - any error should result in a
`{ data: null, errors: [...] }` response - and thus the service can abort
execution when the first error occurs.
-### `onError: NULL`
+### `onError: "NULL"`
+
+> **Clients take responsibility for interpreting the response as a whole...
+> ensuring application code can never read an "error null"**
This is what we're excited about!
-...
+`onError: "NULL"` completely disables error propagation within the GraphQL
+service. From an error perspective, every position in the response (fields and
+lists alike) is an error boundary - as if they are all nullable. This
+effectively changes the "non-nullable" type modifier to mean "null only on
+error".
-We're now proposing a new opt-in execution mode to solve the nullability
-problem. It's important to note that both the client and the server must opt-in
-to this new mode for it to take effect, otherwise the traditional execution mode
-will be used.
+Clients that opt-in to this behavior take responsibility for interpretting the
+response as a whole, correlating the `data` and `errors` properties of the
+response. They must cross-check any `null` values against `errors` to ensure the
+application can never read an "error null"[^1] as if it were a "semantic
+null"[^2].
-### No-error-propagation mode
+[^1]: A `null` that has an associated error in the "errors" list in the response.
-The new proposal centers around the premise of allowing clients to disable the
-"error propagation" behavior discussed above.
+[^2]: A "true" `null` that represents that the data itself doesn't exist - e.g.
+Alice doesn't have a favourite band: `favoriteBand: null`.[^3]
-Clients that opt-in to this behavior take responsibility for interpretting the
-response as a whole, correlating the `data` and `errors` properties of the
-response. With error propagation disabled and the previously discussed fact that
-any field could potentially throw an error, all positions in `data` can
-potentially contain a `null` value. Clients in this mode must cross-check any
-`null` values against `errors` to determine if it represents a true `null`, or
-an error.
+[^3]: About the "u"... Yes, us brits write prose in English but code in american.
### "Smart" clients
From ce6a00ab77100b47d2ae32b1a30e34de3537a447 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 11:31:58 +0100
Subject: [PATCH 06/14] Rewrite end of article
---
.../blog/2026-08-14-true-nullability.mdx | 231 +++++++++++-------
1 file changed, 138 insertions(+), 93 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 716b4f4d13..48f551d334 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -249,10 +249,10 @@ Alice doesn't have a favourite band: `favoriteBand: null`.[^3]
### "Smart" clients
-The no-error-propagation mode is intended for use by "smart" clients such as
-Relay, Apollo Client, URQL and others which understand GraphQL deeply and are
-responsible for the storage and retrieval of fetched GraphQL data. These clients
-are well positioned to handle the responsibilities outlined above.
+`onError: "NULL"` is intended for use by "smart" clients such as Relay, Apollo
+Client, URQL and others which understand GraphQL deeply and are responsible for
+the storage and retrieval of fetched GraphQL data. These clients are well
+positioned to handle the responsibilities outlined above.
By disabling error propagation, these clients will be able to safely update
their stores (including normalized stores) even when errors occur. They can also
@@ -261,98 +261,143 @@ foundations, shielding applications developers from needing to learn this new
behavior (whilst still allowing them to reap the benefits!). They can even take
on advanced behaviors, such as throwing the error when the application developer
attempts to read from an errored field, allowing the developer to handle errors
-with their system's native error boundaries.
+with their system's native error boundaries: `try/catch` or `raise/except` or
+``.
-### True nullability
+### "Error-handling clients"
-Just like in traditional mode, for clients operating in no-error-propagation
-mode fields are either nullable or non-nullable. However; unlike in traditional
-mode, no-error-propagation mode allows for errors to be represented in any
-position:
+An error-handling client is a client that ensures that an "error null" can never
+be read by application code. A client that throws if `errors` exists on a
+response is an error handling client - the `data` can never be read, and so no
+"error nulls" can be read. Clients that implement error handling behaviors such
+as throw-on-error at the field or fragment level also prevent application code
+from reading "error nulls", and so are also error handling clients.
+
+Many clients including `window.fetch()`, Apollo Client, URQL and graffle can be
+made into error-handling clients by integration of something like
+[`graphql-toe`](https://www.npmjs.com/package/graphql-toe) (Throw On Error) - a
+0.5kB library that can be added to JavaScript projects and uses accessors such
+that when application code attempts to read from an errored field, that error is
+thrown so that traditional error handling (`try/catch` or ``)
+can process it.
+
+## True nullability
+
+Just like with traditional error propagation, for clients using `onError:
+"NULL"` fields are either nullable or non-nullable. However; unlike with
+traditional propagation, with `onError: "NULL"`, errors can be represented in
+any position:
- nullable (e.g. `Int`): a value, an error, or a true `null`;
- non-nullable (e.g. `Int!`): a value, **or an error**.
-_(In traditional mode, non-nullable fields cannot represent an error because the
-error propagates to the nearest nullable position.)_
-
-Since this mode allows every field, whether nullable or non-nullable, to
-represent an error, the schema can safely indicate to clients in this mode the
-true intended nullability of a field. If the schema designer knows that a field
-should never be null unless an error occurs, they can mark the field as
-"non-nullable for clients in no-error-propagation mode" (see "schema developers"
-below).
-
-### Client reflection of true nullability
-
-Smart clients can ask the schema about the "true" nullability of each field via
-introspection, and can generate a derived SDL by combining that information with
-their knowledge of how the client handles errors. This derived SDL, dependent on
-client behavior, would look like the traditional representation of the schema,
-but with more fields potentially marked as non-nullable where the true
-nullability of the underlying schema has been reflected. Application developers
-would issue queries and mutations in the same way they always had, but now their
-generated types may not need to handle `null` in as many positions as before,
-increasing developer happiness.
-
-### Schema developers
-
-Schemas that wish to add support for indicating the "true nullability" of a
-field in no-error-propagation mode need to be able to discern which types show
-up as non-nullable in both modes (traditional non-null types), and which types
-show up as non-nullable only in no-error-propagation mode. For this later
-concern we've introduced the concept, of a "semantic" non-null type:
-
-- "strict" (traditional) non-nullable - shows up as non-nullable in both
- traditional mode and no-null-propagation mode
-- "semantic" non-nullable, aka "null only on error" - shows up as non-nullable
- in no-null-propagation mode and masquerades as nullable in traditional mode
-
-Only clients that opt-in to seeing the "true" nullability will see these two
-different types of nullability, otherwise the nullability of the chosen mode
-(traditional or no-error-propagation) will be reflected by introspection.
-
-### Representation in SDL
-
-Application developers will only need to deal with traditional SDL that
-represents traditional nullability concerns. If these developers are using
-"smart" clients then they should source this SDL from the client rather than
-from the server, this allows them to see the nullability that the client
-guarantees based on how it will handle the "true" nullability of the schema, how
-it handles errors, and factoring in any local schema extensions that may have
-been added.
-
-Client-derived SDL (see "client reflection of true nullability" above) can be
-used for concerns such as code generation, which will work in the traditional
-way with no need for changes (but happier developers if there are fewer nullable
-positions!).
-
-Schema developers and people working on "smart" clients may need to represent
-the differences between "strict" and "semantic" non-nullable in SDL. For these
-people, we're introducing the `@extendedNullability` document directive. When
-this directive is present at the top of a document, the `!` symbol means that a
-type will appear as non-nullable only in no-error-propagation mode, and a new
-`!!` symbol will represent that a type will appear as non-nullable in both
-traditional and no-error-propagation mode.
-
-| Traditional Mode | No-error-propagation mode | Example |
-| ---------------- | ------------------------- | ------- |
-| Nullable | Nullable | `Int` |
-| Nullable | Non-nullable | `Int!` |
-| Non-nullable\* | Non-nullable | `Int!!` |
-
-The `!!` symbol is designed to look a little scary - it should be used with
-caution (like `!` in traditional schemas) because it is the symbol that means
-that errors will propagate in traditional mode, "blowing up" parent selection
-sets.
-
-## Get involved
-
-Like all GraphQL Working Groups, the Nullability Working Group is open to all.
-Whether you work on a GraphQL client or are just a GraphQL user with thoughts on
-nullability, we want to hear from you - add yourself to an
-[upcoming working group](https://github.com/graphql/nullability-wg/) or chat
-with us in the #nullability-wg channel in
-[the GraphQL Discord](https://discord.graphql.org). This solution is not yet
-merged into the specification, so there's still time for iteration and
-alternative ideas!
+_(With traditional error propagation, non-nullable fields cannot represent an
+error because the error propagates to the nearest nullable position. Not so with
+`onError: "NULL"`!)_
+
+### Greenfield services
+
+If a GraphQL service can guarantee it will never need to perform error
+propagation (for example by requiring that all clients must include `onError:
+"NULL"` or `onError: "ABORT"` in requests), then the schema can safely indicate
+to clients the true intended nullability of a field in the traditional way -
+with a `!`:
+
+```graphql
+type User {
+ id: ID!
+ username: String!
+ organization: Organization! # Null only on error - a user definitely belongs
+ # to an organization, but the organizations
+ # service might be unavailable.
+
+ mostRecentPost: Post # Deliberately nullable, since you may not have
+ # posted anything yet.
+
+ posts: [Post!]! # No posts? Empty array. Array will never contain
+ # a semantic null.
+}
+```
+
+### Services with legacy clients
+
+For GraphQL services that cannot guarantee that all clients will have error
+propagation disabled, there's a little more work to do.
+
+Traditional clients still need their nullable error boundaries; but for modern
+clients that support `onError: "NULL"` this would still treat these fields as
+truly nullable, requiring null checks in application code that should never
+fire.
+
+We need a way of indicating fields which are "null only on error" whether you're
+using traditional error propagation clients or modern error-handling clients.
+
+For this, we've standardized on the use of the transitional `@semanticNonNull`
+directive until such time as all your clients can be error-handling clients:
+
+```graphql
+type User {
+ id: ID!
+ username: String @semanticNonNull
+ organization: Organization @semanticNonNull
+ mostRecentPost: Post
+ posts: [Post] @semanticNonNull(levels: [0, 1])
+}
+```
+
+`@semanticNonNull` states that a field will only ever be `null` within the
+`data` of the response if there is a matching error in the `errors` list. The
+`levels` argument allows applying this directive to different list positions.
+
+Here's how error-handling clients can interpret various combinations of
+`@semanticNonNull`:
+
+| SDL | Interpretation |
+| --------------------------------------------- | -------------- |
+| `[[Int]]` | `[[Int]]` |
+| `[[Int]] @semanticNonNull` | `[[Int]]!` |
+| `[[Int]] @semanticNonNull(levels: [1])` | `[[Int]!]` |
+| `[[Int]] @semanticNonNull(levels: [0, 1, 2])` | `[[Int!]!]!` |
+| `[[Int]!] @semanticNonNull(levels: [2])` | `[[Int!]!]` |
+
+Of course each client doesn't need to do this themselves,
+[`graphql-sock`](https://www.npmjs.com/package/graphql-sock) (Semantic Output
+Conversion Kit, a great pairing for `graphql-toe`) can be used to read an SDL
+marked up with `@semanticNonNull` and output the equivalent SDL for either
+error-handling clients (`semantic-to-strict`) or for traditional clients
+(`semantic-to-nullable`).
+
+## The future
+
+As clients and servers all adopt `onError: "NULL"`, traditional error
+propagation should become a relic of the past. Application developers will not
+need to look through the `errors` list in a response manually, instead
+error-handling clients will raise errors through ergonomic and familiar patterns
+(for example `Result<...>` types for fragment reads, or simply throwing errors
+when related data is read), fulfilling the promise of "partial success" that
+GraphQL launched with all those years ago.
+
+Once all clients a service serves are error-handling clients, schema designers
+no longer need to factor "errorability" into their schema design. They can
+indicate the true nullability of each field directly through the schema with a
+`!`, clients will need fewer null checks, and the `@semanticNonNull` directive
+can join error propagation as a relic of the past.
+
+Start integrating `onError: "NULL"` into your clients and services today, and
+lets make this future of type safety and solid error handling a reality.
+
+## Help us get this merged!
+
+
+Whether you work on a GraphQL client or server library or framework, or are just
+a GraphQL user with thoughts on nullability, we want to hear from you. Have you
+tried `onError: "NULL"`, `@semanticNonNull`, `graphql-toe` or [other error
+handling
+mechanisms](https://relay.dev/docs/guides/throw-on-field-error-directive/)? Like
+all GraphQL Working Groups, the GraphQL Specification Working Group is open to
+all - add yourself to an [upcoming working
+group](https://github.com/graphql/graphql-wg/) or chat with us in the
+#nullability-wg channel in [the GraphQL Discord](https://discord.graphql.org).
+
+**The solution is formed and ready to go - we just need your adoption and feedback
+to get it merged into the spec!**
From 32fef7f1c31de3e994a9d92575d79e0ffd6deabf Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:11:16 +0100
Subject: [PATCH 07/14] AI SPAG pass
---
.../blog/2026-08-14-true-nullability.mdx | 124 +++++++++---------
1 file changed, 64 insertions(+), 60 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 48f551d334..176d453053 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -7,12 +7,12 @@ byline: Benjie Gillam
One of GraphQL's early decisions was to allow "partial success"; this was a
critical feature for Facebook - if one part of their backend infrastructure
-became degraded they wouldn't want to just render an error page, instead they
-wanted to serve the user a page with as much working data as they could.
+became degraded, they wouldn't want to just render an error page; instead,
+they wanted to serve the user a page with as much working data as they could.
## Null propagation
-To accomplish this, if an error occured within a resolver, the resolver's value
+To accomplish this, if an error occurred within a resolver, the resolver's value
would be replaced with a `null`, and an error would be added to the `errors`
array in the response.
@@ -21,15 +21,15 @@ But what if that field was marked as non-null?
To solve that apparent contradiction, GraphQL introduced "error propagation"
(aka "null bubbling"): when a `null` occurs in a non-nullable position, the
parent position is made `null` instead. If that position is also non-nullable,
-it's parent will be made `null` instead, and so on up the tree until a nullable
+its parent will be made `null` instead, and so on up the tree until a nullable
position is made `null`.
This solved the issue, and meant that GraphQL's nullability promises were still
-honoured; but it wasn't without complications...
+honoured, but it wasn't without complications...
### Complication 1: partial success
-We want to be resilient to systems failing; but errors that occur in
+We want to be resilient to systems failing, but errors that occur in
non-nullable positions cascade to surrounding parts of the query, making less
and less data available to be rendered.
@@ -50,31 +50,31 @@ unexpected ways; they can also be caused by simple programming errors in your
business logic, data consistency errors (e.g. expecting a boolean but receiving
a float), access controls, or any other cause.
-Since we don't want to "blow up" the entire response if any such issue occurred,
+Since we don't want to "blow up" the entire response if any such issue occurs,
we've moved to strongly encourage nullable usage throughout a schema, only
adding the non-nullable `!` marker to positions where we're truly sure that
field is extremely unlikely to error.
This "nullable by default" has the effect of meaning that developers consuming
the GraphQL API have to handle potential nulls in more positions than they would
-expect, an explosion of null checks leading people to even call into question
-the value of GraphQL's "type safety".
+expect, with an explosion of null checks leading people to even call into
+question the value of GraphQL's "type safety".
### Complication 3: normalized caching
Many modern GraphQL clients use a "normalized" cache, such that updates pulled
down from the API in one query can automatically update all the previously
-rendered data across the application. This helps ensure consistency for users,
+rendered data across the application. This helps ensure consistency for users
and is a powerful feature.
However, if an error occurs in a non-nullable position, it's
[no longer safe](https://github.com/graphql/nullability-wg/issues/20) to store
-the data to the normalized cache.
+the data in the normalized cache.
## The Nullability Working Group
At first, we thought the solution to this was to give clients control over the
-nullability of each field in response, so we set up the Client-Controlled
+nullability of each field in the response, so we set up the Client-Controlled
Nullability (CCN) Working Group. Later, we renamed the working group to the
Nullability WG to show that it encompassed all potential solutions to this
problem.
@@ -83,18 +83,18 @@ problem.
The first Nullability WG proposal came from a collaboration between Yelp and
Netflix, with contributions from GraphQL WG regulars Alex Reilly, Mark Larah,
-and Stephen Spalding among others. They proposed we could adorn the queries we
+and Stephen Spalding, among others. They proposed we could adorn the queries we
issue to the server with sigils indicating our desired nullability overrides for
the given fields - client-controlled nullability.
-A `?` would be added to fields where we don't mind if they're null, but we
-definitely want errors to stop there; and add a `!` to fields where we
-definitely don't want a null to occur (whether or not there is an error). This
-would give consumers control over where errors/nulls were handled.
+A `?` would be added to fields where we don't mind if they're null but we
+definitely want errors to stop there, and a `!` would be added to fields
+where we definitely don't want a null to occur (whether or not there is an
+error). This would give consumers control over where errors/nulls were handled.
-However, after much exploration of the topic over years we found numerous issues
-that traded one set of concerns for another. We kept iterating whilst we looked
-for a solution to these tradeoffs.
+However, after much exploration of the topic over the years, we found
+numerous issues that traded one set of concerns for another. We kept iterating
+whilst we looked for a solution to these trade-offs.
### True nullability schema
@@ -121,19 +121,19 @@ be handled, but would not "blow up" the response.
To maintain backwards compatibility, clients would need to opt in to seeing this
new type (otherwise it would masquerade as nullable). It would be up to the
-client how to handle the nullability of this position knowing that a "null only
+client how to handle the nullability of this position, knowing that a "null only
on error" position would only contain a `null` if a matching error existed in
the `errors` list.
A
[number of alternative syntaxes](https://gist.github.com/benjie/19d784721d1658b89fd8954e7ee07034)
-were suggested for this new type, but none were well liked.
+were suggested for this new type, but none were well-liked.
### A new approach to client error handling
-Also around the time of GraphQLConf 2023 the Relay team shared
+Also around the time of GraphQLConf 2023, the Relay team shared
[a presentation](https://docs.google.com/presentation/u/2/d/1rfWeBcyJkiNqyxPxUIKxgbExmfdjA70t/edit?pli=1#slide=id.p8)
-on some of the things they were thinking around errors. In particular they
+on some of the things they were thinking around errors. In particular, they
discussed the `@catch` directive which would give users control over how errors
were represented in the data being rendered, allowing the client to
differentiate an error from a legitimate null. Over the coming months, many
@@ -149,8 +149,8 @@ GraphQL Foundation director Lee Byron
[proposed](https://github.com/graphql/graphql-wg/discussions/1410) that we
introduce a schema directive, `@strictNullability`, whereby we would change what
the syntax meant - `Int?` for nullable, `Int` for null-only-on-error, and `Int!`
-for never-null. This proposal was well liked, but wasn't a clear win; it
-introduced many complexities including migration costs and concerns over schema
+for never-null. This proposal was well-liked, but wasn't a clear win; it
+introduced many complexities, including migration costs and concerns over schema
evolution.
### A pivotal discussion
@@ -160,7 +160,7 @@ nullability and all the relevant proposals in depth, including their two
respective solutions. It was clear that though no solution was quite there, the
solutions converging hinted we were getting closer and closer to an answer.
-This long and detailed highly technical discussion ultimately led to the
+This long, detailed, highly technical discussion ultimately led to the
realisation that error propagation itself was the issue. Rather than
working around error propagation with new "null only on error" types, what we
really needed was a way for "smart clients" to turn off error propagation
@@ -170,17 +170,17 @@ entirely.
Our first punt at this was the `@experimental_disableErrorPropagation` directive
that could be added to operations to disable error propagation. However, we
-quickly realised that this was cumbersome, inconsistent, and also became the
-responsibility of the developer rather than the client. A smart client that
-understands the schema should be able to fully re-implement traditional error
-propagation locally: `data` and `errors` contains all the information it would
-need to do so. And if a client supports this, it would want to disable error
-propagation for every request...
+quickly realised that this was cumbersome and inconsistent, and that disabling
+error propagation also became the responsibility of the developer rather than
+the client. A smart client that understands the schema should be able to fully
+re-implement traditional error propagation locally: `data` and `errors` contain
+all the information it would need to do so. And if a client supports this, it
+would want to disable error propagation for every request...
## Introducing the `onError` request property
The new `onError` request property allows a client to indicate its preference as
-to how errors are handled by the GraphQL service (each option detailed below).
+to how errors are handled by the GraphQL service (each option is detailed below).
For example:
```diff
@@ -197,8 +197,8 @@ For example:
```
Services that support `onError` must honor the specified behavior. If a service
-does not support `onError` then the request property will be ignored, resulting
-in the traditional behavior - equivalent to `onError: PROPAGATE`.
+does not support `onError`, then the request property will be ignored, resulting
+in the traditional behavior - equivalent to `onError: "PROPAGATE"`.
Services will soon be able to indicate their support for the `onError` request
property through [service
@@ -209,7 +209,7 @@ clients to auto-discover and depend upon the capability.
This is the traditional error propagation behavior that we all know and... "love"?
-Setting `onError: PROPAGATE` will be equivalent to the error behavior in the
+Setting `onError: "PROPAGATE"` will be equivalent to the error behavior in the
initial 2015 GraphQL Specification: error propagation/null bubbling.
### `onError: "ABORT"`
@@ -217,7 +217,7 @@ initial 2015 GraphQL Specification: error propagation/null bubbling.
Ad-hoc scripts and similar clients throw away entire responses if any error
occurs, but currently the server still computes the "partial success" response
anyway. This mode allows clients to indicate that if any error occurs, they
-won't be reading the data - any error should result in a
+won't be reading the data: any error should result in a
`{ data: null, errors: [...] }` response - and thus the service can abort
execution when the first error occurs.
@@ -234,7 +234,7 @@ lists alike) is an error boundary - as if they are all nullable. This
effectively changes the "non-nullable" type modifier to mean "null only on
error".
-Clients that opt-in to this behavior take responsibility for interpretting the
+Clients that opt in to this behavior take responsibility for interpreting the
response as a whole, correlating the `data` and `errors` properties of the
response. They must cross-check any `null` values against `errors` to ensure the
application can never read an "error null"[^1] as if it were a "semantic
@@ -245,19 +245,19 @@ null"[^2].
[^2]: A "true" `null` that represents that the data itself doesn't exist - e.g.
Alice doesn't have a favourite band: `favoriteBand: null`.[^3]
-[^3]: About the "u"... Yes, us brits write prose in English but code in american.
+[^3]: About the "u"... Yes, we Brits write prose in English but code in American.
### "Smart" clients
`onError: "NULL"` is intended for use by "smart" clients such as Relay, Apollo
-Client, URQL and others which understand GraphQL deeply and are responsible for
+Client, URQL and others that understand GraphQL deeply and are responsible for
the storage and retrieval of fetched GraphQL data. These clients are well
positioned to handle the responsibilities outlined above.
By disabling error propagation, these clients will be able to safely update
their stores (including normalized stores) even when errors occur. They can also
re-implement traditional GraphQL error propagation on top of these new
-foundations, shielding applications developers from needing to learn this new
+foundations, shielding application developers from needing to learn this new
behavior (whilst still allowing them to reap the benefits!). They can even take
on advanced behaviors, such as throwing the error when the application developer
attempts to read from an errored field, allowing the developer to handle errors
@@ -268,13 +268,13 @@ with their system's native error boundaries: `try/catch` or `raise/except` or
An error-handling client is a client that ensures that an "error null" can never
be read by application code. A client that throws if `errors` exists on a
-response is an error handling client - the `data` can never be read, and so no
-"error nulls" can be read. Clients that implement error handling behaviors such
+response is an error-handling client - the `data` can never be read, and so no
+"error nulls" can be read. Clients that implement error-handling behaviors such
as throw-on-error at the field or fragment level also prevent application code
-from reading "error nulls", and so are also error handling clients.
+from reading "error nulls", and so are also error-handling clients.
-Many clients including `window.fetch()`, Apollo Client, URQL and graffle can be
-made into error-handling clients by integration of something like
+Many clients, including `window.fetch()`, Apollo Client, URQL and graffle, can be
+made into error-handling clients by integrating something like
[`graphql-toe`](https://www.npmjs.com/package/graphql-toe) (Throw On Error) - a
0.5kB library that can be added to JavaScript projects and uses accessors such
that when application code attempts to read from an errored field, that error is
@@ -284,7 +284,7 @@ can process it.
## True nullability
Just like with traditional error propagation, for clients using `onError:
-"NULL"` fields are either nullable or non-nullable. However; unlike with
+"NULL"`, fields are either nullable or non-nullable. However, unlike with
traditional propagation, with `onError: "NULL"`, errors can be represented in
any position:
@@ -324,7 +324,7 @@ type User {
For GraphQL services that cannot guarantee that all clients will have error
propagation disabled, there's a little more work to do.
-Traditional clients still need their nullable error boundaries; but for modern
+Traditional clients still need their nullable error boundaries, but for modern
clients that support `onError: "NULL"` this would still treat these fields as
truly nullable, requiring null checks in application code that should never
fire.
@@ -360,44 +360,48 @@ Here's how error-handling clients can interpret various combinations of
| `[[Int]] @semanticNonNull(levels: [0, 1, 2])` | `[[Int!]!]!` |
| `[[Int]!] @semanticNonNull(levels: [2])` | `[[Int!]!]` |
-Of course each client doesn't need to do this themselves,
+Of course, each client doesn't need to do this themselves;
[`graphql-sock`](https://www.npmjs.com/package/graphql-sock) (Semantic Output
Conversion Kit, a great pairing for `graphql-toe`) can be used to read an SDL
marked up with `@semanticNonNull` and output the equivalent SDL for either
-error-handling clients (`semantic-to-strict`) or for traditional clients
+error-handling clients (`semantic-to-strict`) or traditional clients
(`semantic-to-nullable`).
## The future
As clients and servers all adopt `onError: "NULL"`, traditional error
propagation should become a relic of the past. Application developers will not
-need to look through the `errors` list in a response manually, instead
+need to look through the `errors` list in a response manually; instead,
error-handling clients will raise errors through ergonomic and familiar patterns
(for example `Result<...>` types for fragment reads, or simply throwing errors
when related data is read), fulfilling the promise of "partial success" that
GraphQL launched with all those years ago.
-Once all clients a service serves are error-handling clients, schema designers
-no longer need to factor "errorability" into their schema design. They can
-indicate the true nullability of each field directly through the schema with a
-`!`, clients will need fewer null checks, and the `@semanticNonNull` directive
+Once all the clients a service serves are error-handling clients, schema
+designers no longer need to factor "errorability" into their schema design. They
+can indicate the true nullability of each field directly through the schema with a
+`!`; clients will need fewer null checks, and the `@semanticNonNull` directive
can join error propagation as a relic of the past.
Start integrating `onError: "NULL"` into your clients and services today, and
-lets make this future of type safety and solid error handling a reality.
+let's make this future of type safety and solid error handling a reality.
## Help us get this merged!
-
Whether you work on a GraphQL client or server library or framework, or are just
a GraphQL user with thoughts on nullability, we want to hear from you. Have you
-tried `onError: "NULL"`, `@semanticNonNull`, `graphql-toe` or [other error
+tried `onError: "NULL"`, `@semanticNonNull`, `graphql-toe`, or [other error
handling
mechanisms](https://relay.dev/docs/guides/throw-on-field-error-directive/)? Like
all GraphQL Working Groups, the GraphQL Specification Working Group is open to
-all - add yourself to an [upcoming working
+all: add yourself to an [upcoming working
group](https://github.com/graphql/graphql-wg/) or chat with us in the
#nullability-wg channel in [the GraphQL Discord](https://discord.graphql.org).
**The solution is formed and ready to go - we just need your adoption and feedback
to get it merged into the spec!**
+
+---
+
+spag.inputfrom.ai
From 56e3b5822863a134ba49f57d8b31032e57b1bf74 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:12:46 +0100
Subject: [PATCH 08/14] Jump
---
src/pages/blog/2026-08-14-true-nullability.mdx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 176d453053..cd31b338ff 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -5,6 +5,9 @@ date: 2026-09-10
byline: Benjie Gillam
---
+_Want to skip the history lesson and get to the good stuff? Jump to [Introducing
+the `onError` request property](#introducing-the-onerror-request-property)._
+
One of GraphQL's early decisions was to allow "partial success"; this was a
critical feature for Facebook - if one part of their backend infrastructure
became degraded, they wouldn't want to just render an error page; instead,
From af582ef89d8b24690749227a1367c64e420bb01c Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:22:08 +0100
Subject: [PATCH 09/14] Fix date
---
src/pages/blog/2026-08-14-true-nullability.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index cd31b338ff..f41da8b362 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -1,7 +1,7 @@
---
title: "'True' Nullability is coming to GraphQL"
tags: ["spec"]
-date: 2026-09-10
+date: 2026-08-14
byline: Benjie Gillam
---
From 890707a0d66e06a53a589b1d60a6bf833d714916 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:23:41 +0100
Subject: [PATCH 10/14] Correct field -> position
---
src/pages/blog/2026-08-14-true-nullability.mdx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index f41da8b362..0831c0a91a 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -15,11 +15,11 @@ they wanted to serve the user a page with as much working data as they could.
## Null propagation
-To accomplish this, if an error occurred within a resolver, the resolver's value
-would be replaced with a `null`, and an error would be added to the `errors`
-array in the response.
+To accomplish this, if an error occurred within a resolver, that field's
+response position would be replaced with a `null`, and an error would be added
+to the `errors` array in the response.
-But what if that field was marked as non-null?
+But what if that position was marked as non-null?
To solve that apparent contradiction, GraphQL introduced "error propagation"
(aka "null bubbling"): when a `null` occurs in a non-nullable position, the
From 9063ce1b824048ff1037db2b2440e90bff2936e7 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:25:21 +0100
Subject: [PATCH 11/14] Better title?
---
src/pages/blog/2026-08-14-true-nullability.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 0831c0a91a..4822248ed1 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -1,5 +1,5 @@
---
-title: "'True' Nullability is coming to GraphQL"
+title: "Bringing 'True' Nullability to GraphQL"
tags: ["spec"]
date: 2026-08-14
byline: Benjie Gillam
From 699e6864d3f352eddcee5d7743942fbbb65e3682 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Sat, 25 Jul 2026 12:31:40 +0100
Subject: [PATCH 12/14] AI consistency and clarity pass.
---
.../blog/2026-08-14-true-nullability.mdx | 229 +++++++++---------
1 file changed, 115 insertions(+), 114 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 4822248ed1..7ed2126f42 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -9,14 +9,14 @@ _Want to skip the history lesson and get to the good stuff? Jump to [Introducing
the `onError` request property](#introducing-the-onerror-request-property)._
One of GraphQL's early decisions was to allow "partial success"; this was a
-critical feature for Facebook - if one part of their backend infrastructure
+critical feature for Facebook: if one part of their backend infrastructure
became degraded, they wouldn't want to just render an error page; instead,
they wanted to serve the user a page with as much working data as they could.
## Null propagation
To accomplish this, if an error occurred within a resolver, that field's
-response position would be replaced with a `null`, and an error would be added
+response position would be replaced with `null`, and an error would be added
to the `errors` array in the response.
But what if that position was marked as non-null?
@@ -27,7 +27,7 @@ parent position is made `null` instead. If that position is also non-nullable,
its parent will be made `null` instead, and so on up the tree until a nullable
position is made `null`.
-This solved the issue, and meant that GraphQL's nullability promises were still
+This solved the issue and meant that GraphQL's nullability promises were still
honoured, but it wasn't without complications...
### Complication 1: partial success
@@ -36,59 +36,59 @@ We want to be resilient to systems failing, but errors that occur in
non-nullable positions cascade to surrounding parts of the query, making less
and less data available to be rendered.
-This seems contrary to our "partial success" aim, but it's easy to solve - we
+This seems contrary to our "partial success" aim, but it's easy to solve: we
just make sure that the positions where we expect errors to occur are nullable
so that errors don't propagate further.
-Unfortunately this means clients now need `null`-handling code in a few more
-places, but what is engineering if not choosing trade-offs...
+Unfortunately, this means clients now need `null`-handling code in a few more
+places, but what is engineering if not choosing trade-offs?
### Complication 2: nullable epidemic
So... where are errors likely to occur?
-Almost any field in your GraphQL schema could raise an error - errors might not
+Almost any field in your GraphQL schema could raise an error. Errors might not
only be caused by backend services becoming unavailable or responding in
unexpected ways; they can also be caused by simple programming errors in your
-business logic, data consistency errors (e.g. expecting a boolean but receiving
+business logic, data consistency errors (e.g., expecting a boolean but receiving
a float), access controls, or any other cause.
Since we don't want to "blow up" the entire response if any such issue occurs,
-we've moved to strongly encourage nullable usage throughout a schema, only
-adding the non-nullable `!` marker to positions where we're truly sure that
+we've come to strongly encourage nullable usage throughout a schema, only
+adding the non-nullable `!` marker to positions where we're truly sure that the
field is extremely unlikely to error.
-This "nullable by default" has the effect of meaning that developers consuming
-the GraphQL API have to handle potential nulls in more positions than they would
-expect, with an explosion of null checks leading people to even call into
-question the value of GraphQL's "type safety".
+This "nullable by default" approach means that developers consuming the GraphQL
+API have to handle potential nulls in more positions than they would expect,
+with an explosion of null checks leading people to even call into question the
+value of GraphQL's "type safety".
-### Complication 3: normalized caching
+### Complication 3: normalised caching
-Many modern GraphQL clients use a "normalized" cache, such that updates pulled
+Many modern GraphQL clients use a "normalised" cache, such that updates pulled
down from the API in one query can automatically update all the previously
rendered data across the application. This helps ensure consistency for users
and is a powerful feature.
However, if an error occurs in a non-nullable position, it's
[no longer safe](https://github.com/graphql/nullability-wg/issues/20) to store
-the data in the normalized cache.
+the data in the normalised cache.
## The Nullability Working Group
-At first, we thought the solution to this was to give clients control over the
-nullability of each field in the response, so we set up the Client-Controlled
-Nullability (CCN) Working Group. Later, we renamed the working group to the
-Nullability WG to show that it encompassed all potential solutions to this
-problem.
+At first, we thought the solution to these complications was to give clients
+control over the nullability of each field in the response, so we set up the
+Client-Controlled Nullability (CCN) Working Group. Later, we renamed the working
+group to the Nullability WG to show that it encompassed all potential solutions
+to this problem.
### Client-controlled nullability
The first Nullability WG proposal came from a collaboration between Yelp and
Netflix, with contributions from GraphQL WG regulars Alex Reilly, Mark Larah,
-and Stephen Spalding, among others. They proposed we could adorn the queries we
-issue to the server with sigils indicating our desired nullability overrides for
-the given fields - client-controlled nullability.
+and Stephen Spalding, among others. They proposed that we could adorn the queries
+we issue to the server with sigils indicating our desired nullability overrides
+for the given fields: client-controlled nullability.
A `?` would be added to fields where we don't mind if they're null but we
definitely want errors to stop there, and a `!` would be added to fields
@@ -114,19 +114,19 @@ Relay desired was to disable null propagation entirely.
### A new type
-Getting the relevant experts together at GraphQLConf 2023 re-energized the
+Getting the relevant experts together at GraphQLConf 2023 re-energised the
discussions and sparked new ideas. After seeing Stephen's "Nullability Sandwich"
-talk and chatting with Jordan, Stephen and others in the corridor, Benjie Gillam
+talk and chatting with Jordan, Stephen, and others in the corridor, Benjie Gillam
was inspired to [propose](https://github.com/graphql/graphql-spec/pull/1046) a
"null only on error" type. This type would allow us to express the "true"
-nullability of a field whilst also indicating that errors may happen that should
-be handled, but would not "blow up" the response.
+nullability of a field whilst also indicating that errors may occur and should
+be handled without "blowing up" the response.
To maintain backwards compatibility, clients would need to opt in to seeing this
new type (otherwise it would masquerade as nullable). It would be up to the
-client how to handle the nullability of this position, knowing that a "null only
-on error" position would only contain a `null` if a matching error existed in
-the `errors` list.
+client to decide how to handle the nullability of this position, knowing that a
+"null only on error" position would only contain a `null` if a matching error
+existed in the `errors` list.
A
[number of alternative syntaxes](https://gist.github.com/benjie/19d784721d1658b89fd8954e7ee07034)
@@ -137,21 +137,21 @@ were suggested for this new type, but none were well-liked.
Also around the time of GraphQLConf 2023, the Relay team shared
[a presentation](https://docs.google.com/presentation/u/2/d/1rfWeBcyJkiNqyxPxUIKxgbExmfdjA70t/edit?pli=1#slide=id.p8)
on some of the things they were thinking around errors. In particular, they
-discussed the `@catch` directive which would give users control over how errors
+discussed the `@catch` directive, which would give users control over how errors
were represented in the data being rendered, allowing the client to
differentiate an error from a legitimate null. Over the coming months, many
-behaviors were discussed at the Nullability WG; one particularly compelling one
-was that clients could throw the error when an errored field was read, and rely
+behaviours were discussed at the Nullability WG; one particularly compelling one
+was that clients could throw the error when an errored field was read and rely
on framework mechanics (such as React's
[error boundaries](https://legacy.reactjs.org/docs/error-boundaries.html)) to
-handle them.
+handle those errors.
### Strict semantic nullability
GraphQL Foundation director Lee Byron
[proposed](https://github.com/graphql/graphql-wg/discussions/1410) that we
introduce a schema directive, `@strictNullability`, whereby we would change what
-the syntax meant - `Int?` for nullable, `Int` for null-only-on-error, and `Int!`
+the syntax meant: `Int?` for nullable, `Int` for null-only-on-error, and `Int!`
for never-null. This proposal was well-liked, but wasn't a clear win; it
introduced many complexities, including migration costs and concerns over schema
evolution.
@@ -160,8 +160,9 @@ evolution.
Lee and Benjie had a call where they discussed the history of GraphQL
nullability and all the relevant proposals in depth, including their two
-respective solutions. It was clear that though no solution was quite there, the
-solutions converging hinted we were getting closer and closer to an answer.
+respective solutions. It was clear that, though no solution was quite there,
+the solutions were converging, hinting that we were getting closer and closer to
+an answer.
This long, detailed, highly technical discussion ultimately led to the
realisation that error propagation itself was the issue. Rather than
@@ -174,17 +175,17 @@ entirely.
Our first punt at this was the `@experimental_disableErrorPropagation` directive
that could be added to operations to disable error propagation. However, we
quickly realised that this was cumbersome and inconsistent, and that disabling
-error propagation also became the responsibility of the developer rather than
-the client. A smart client that understands the schema should be able to fully
-re-implement traditional error propagation locally: `data` and `errors` contain
-all the information it would need to do so. And if a client supports this, it
-would want to disable error propagation for every request...
+error propagation would also become the responsibility of the developer rather
+than the client. A smart client that understands the schema should be able to
+fully re-implement traditional error propagation locally: `data` and `errors`
+contain all the information it would need to do so. And if a client supports
+this, it would want to disable error propagation for every request...
## Introducing the `onError` request property
-The new `onError` request property allows a client to indicate its preference as
-to how errors are handled by the GraphQL service (each option is detailed below).
-For example:
+The new `onError` request property allows a client to indicate its preference
+for how errors are handled by the GraphQL service (each option is detailed
+below). For example:
```diff
POST /graphql HTTP/1.1
@@ -199,20 +200,21 @@ For example:
}
```
-Services that support `onError` must honor the specified behavior. If a service
-does not support `onError`, then the request property will be ignored, resulting
-in the traditional behavior - equivalent to `onError: "PROPAGATE"`.
+Services that support `onError` must honour the specified behaviour. If a
+service does not support `onError`, the request property will be ignored,
+resulting in the traditional behaviour, equivalent to `onError: "PROPAGATE"`.
Services will soon be able to indicate their support for the `onError` request
property through [service
capabilities](https://github.com/graphql/graphql-spec/pull/1208)), allowing
-clients to auto-discover and depend upon the capability.
+clients to auto-discover and depend upon this capability.
### `onError: "PROPAGATE"`
-This is the traditional error propagation behavior that we all know and... "love"?
+This is the traditional error propagation behaviour that we all know and...
+"love"?
-Setting `onError: "PROPAGATE"` will be equivalent to the error behavior in the
+Setting `onError: "PROPAGATE"` will be equivalent to the error behaviour in the
initial 2015 GraphQL Specification: error propagation/null bubbling.
### `onError: "ABORT"`
@@ -220,8 +222,8 @@ initial 2015 GraphQL Specification: error propagation/null bubbling.
Ad-hoc scripts and similar clients throw away entire responses if any error
occurs, but currently the server still computes the "partial success" response
anyway. This mode allows clients to indicate that if any error occurs, they
-won't be reading the data: any error should result in a
-`{ data: null, errors: [...] }` response - and thus the service can abort
+won't read the data: any error should result in a
+`{ data: null, errors: [...] }` response, allowing the service to abort
execution when the first error occurs.
### `onError: "NULL"`
@@ -233,11 +235,10 @@ This is what we're excited about!
`onError: "NULL"` completely disables error propagation within the GraphQL
service. From an error perspective, every position in the response (fields and
-lists alike) is an error boundary - as if they are all nullable. This
-effectively changes the "non-nullable" type modifier to mean "null only on
-error".
+lists alike) is an error boundary, as though it were nullable. This effectively
+changes the "non-nullable" type modifier to mean "null only on error".
-Clients that opt in to this behavior take responsibility for interpreting the
+Clients that opt in to this behaviour take responsibility for interpreting the
response as a whole, correlating the `data` and `errors` properties of the
response. They must cross-check any `null` values against `errors` to ensure the
application can never read an "error null"[^1] as if it were a "semantic
@@ -245,65 +246,64 @@ null"[^2].
[^1]: A `null` that has an associated error in the "errors" list in the response.
-[^2]: A "true" `null` that represents that the data itself doesn't exist - e.g.
-Alice doesn't have a favourite band: `favoriteBand: null`.[^3]
+[^2]: A "true" `null` representing that the data itself doesn't exist: e.g.,
+ Alice doesn't have a favourite band: `favoriteBand: null`.[^3]
[^3]: About the "u"... Yes, we Brits write prose in English but code in American.
### "Smart" clients
`onError: "NULL"` is intended for use by "smart" clients such as Relay, Apollo
-Client, URQL and others that understand GraphQL deeply and are responsible for
+Client, URQL, and others that understand GraphQL deeply and are responsible for
the storage and retrieval of fetched GraphQL data. These clients are well
positioned to handle the responsibilities outlined above.
By disabling error propagation, these clients will be able to safely update
-their stores (including normalized stores) even when errors occur. They can also
+their stores (including normalised stores) even when errors occur. They can also
re-implement traditional GraphQL error propagation on top of these new
foundations, shielding application developers from needing to learn this new
-behavior (whilst still allowing them to reap the benefits!). They can even take
-on advanced behaviors, such as throwing the error when the application developer
-attempts to read from an errored field, allowing the developer to handle errors
-with their system's native error boundaries: `try/catch` or `raise/except` or
-``.
+behaviour (whilst still allowing them to reap the benefits!). They can even take
+on advanced behaviours, such as throwing the error when the application
+developer attempts to read from an errored field, allowing the developer to
+handle errors with their system's native error boundaries: `try/catch`,
+`raise/except`, or ``.
### "Error-handling clients"
An error-handling client is a client that ensures that an "error null" can never
-be read by application code. A client that throws if `errors` exists on a
-response is an error-handling client - the `data` can never be read, and so no
-"error nulls" can be read. Clients that implement error-handling behaviors such
+be read by application code. A client that throws if an `errors` property exists
+in a response is an error-handling client; the `data` can never be read, so no
+"error nulls" can be read. Clients that implement error-handling behaviours such
as throw-on-error at the field or fragment level also prevent application code
-from reading "error nulls", and so are also error-handling clients.
+from reading "error nulls" and are therefore also error-handling clients.
-Many clients, including `window.fetch()`, Apollo Client, URQL and graffle, can be
-made into error-handling clients by integrating something like
-[`graphql-toe`](https://www.npmjs.com/package/graphql-toe) (Throw On Error) - a
-0.5kB library that can be added to JavaScript projects and uses accessors such
-that when application code attempts to read from an errored field, that error is
-thrown so that traditional error handling (`try/catch` or ``)
-can process it.
+Many clients, including `window.fetch()`, Apollo Client, URQL, and graffle, can
+be made into error-handling clients by integrating something like
+[`graphql-toe`](https://www.npmjs.com/package/graphql-toe) (Throw On Error): a
+0.5kB library that can be added to JavaScript projects and uses accessors so
+that, when application code attempts to read from an errored field, that error
+is thrown and can be processed by traditional error handling (`try/catch` or
+``).
## True nullability
-Just like with traditional error propagation, for clients using `onError:
-"NULL"`, fields are either nullable or non-nullable. However, unlike with
-traditional propagation, with `onError: "NULL"`, errors can be represented in
-any position:
+For clients using `onError: "NULL"`, fields are either nullable or non-nullable,
+just as with traditional error propagation. However, unlike with traditional
+propagation, errors can be represented in any position:
-- nullable (e.g. `Int`): a value, an error, or a true `null`;
-- non-nullable (e.g. `Int!`): a value, **or an error**.
+* nullable (e.g., `Int`): a value, an error, or a true `null`;
+* non-nullable (e.g., `Int!`): a value, **or an error**.
-_(With traditional error propagation, non-nullable fields cannot represent an
+*(With traditional error propagation, non-nullable fields cannot represent an
error because the error propagates to the nearest nullable position. Not so with
-`onError: "NULL"`!)_
+`onError: "NULL"`!)*
### Greenfield services
If a GraphQL service can guarantee it will never need to perform error
-propagation (for example by requiring that all clients must include `onError:
-"NULL"` or `onError: "ABORT"` in requests), then the schema can safely indicate
-to clients the true intended nullability of a field in the traditional way -
+propagation (for example, by requiring all clients to include `onError: "NULL"`
+or `onError: "ABORT"` in requests), then the schema can safely indicate to
+clients the true intended nullability of a field in the traditional way:
with a `!`:
```graphql
@@ -327,15 +327,15 @@ type User {
For GraphQL services that cannot guarantee that all clients will have error
propagation disabled, there's a little more work to do.
-Traditional clients still need their nullable error boundaries, but for modern
-clients that support `onError: "NULL"` this would still treat these fields as
-truly nullable, requiring null checks in application code that should never
-fire.
+Traditional clients still need their nullable error boundaries, but modern
+clients that support `onError: "NULL"` would still treat these fields as truly
+nullable, requiring null checks in application code that should never fire.
-We need a way of indicating fields which are "null only on error" whether you're
-using traditional error propagation clients or modern error-handling clients.
+We need a way of indicating fields that are "null only on error", whether you're
+using traditional clients with error propagation or modern error-handling
+clients.
-For this, we've standardized on the use of the transitional `@semanticNonNull`
+For this, we've standardised on the use of the transitional `@semanticNonNull`
directive until such time as all your clients can be error-handling clients:
```graphql
@@ -350,7 +350,8 @@ type User {
`@semanticNonNull` states that a field will only ever be `null` within the
`data` of the response if there is a matching error in the `errors` list. The
-`levels` argument allows applying this directive to different list positions.
+`levels` argument allows this directive to be applied to different list
+positions.
Here's how error-handling clients can interpret various combinations of
`@semanticNonNull`:
@@ -363,7 +364,7 @@ Here's how error-handling clients can interpret various combinations of
| `[[Int]] @semanticNonNull(levels: [0, 1, 2])` | `[[Int!]!]!` |
| `[[Int]!] @semanticNonNull(levels: [2])` | `[[Int!]!]` |
-Of course, each client doesn't need to do this themselves;
+Of course, clients don't need to do this themselves;
[`graphql-sock`](https://www.npmjs.com/package/graphql-sock) (Semantic Output
Conversion Kit, a great pairing for `graphql-toe`) can be used to read an SDL
marked up with `@semanticNonNull` and output the equivalent SDL for either
@@ -375,36 +376,36 @@ error-handling clients (`semantic-to-strict`) or traditional clients
As clients and servers all adopt `onError: "NULL"`, traditional error
propagation should become a relic of the past. Application developers will not
need to look through the `errors` list in a response manually; instead,
-error-handling clients will raise errors through ergonomic and familiar patterns
-(for example `Result<...>` types for fragment reads, or simply throwing errors
+error-handling clients will raise errors using ergonomic and familiar patterns
+(for example, `Result<...>` types for fragment reads or simply throwing errors
when related data is read), fulfilling the promise of "partial success" that
-GraphQL launched with all those years ago.
+came with GraphQL's launch all those years ago.
Once all the clients a service serves are error-handling clients, schema
designers no longer need to factor "errorability" into their schema design. They
-can indicate the true nullability of each field directly through the schema with a
-`!`; clients will need fewer null checks, and the `@semanticNonNull` directive
-can join error propagation as a relic of the past.
+can indicate the true nullability of each field directly through the schema
+with a `!`; clients will need fewer null checks, and the `@semanticNonNull`
+directive can join error propagation as a relic of the past.
Start integrating `onError: "NULL"` into your clients and services today, and
let's make this future of type safety and solid error handling a reality.
## Help us get this merged!
-Whether you work on a GraphQL client or server library or framework, or are just
-a GraphQL user with thoughts on nullability, we want to hear from you. Have you
-tried `onError: "NULL"`, `@semanticNonNull`, `graphql-toe`, or [other error
-handling
+Whether you work on a GraphQL client library, server library, or framework, or
+are just a GraphQL user with thoughts on nullability, we want to hear from you.
+Have you tried `onError: "NULL"`, `@semanticNonNull`, `graphql-toe`, or [other
+error handling
mechanisms](https://relay.dev/docs/guides/throw-on-field-error-directive/)? Like
all GraphQL Working Groups, the GraphQL Specification Working Group is open to
-all: add yourself to an [upcoming working
-group](https://github.com/graphql/graphql-wg/) or chat with us in the
+all: add yourself to an [upcoming working group
+meeting](https://github.com/graphql/graphql-wg/) or chat with us in the
#nullability-wg channel in [the GraphQL Discord](https://discord.graphql.org).
-**The solution is formed and ready to go - we just need your adoption and feedback
-to get it merged into the spec!**
+**The solution is fully formed and ready to go; we just need your adoption and
+feedback to get it merged into the spec!**
---
spag.inputfrom.ai
+href="https://clarity.inputfrom.ai/">clarity.inputfrom.ai
From 8ddbb6e0e7ac46385b8cdbc1eef271d742e9583b Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Mon, 3 Aug 2026 12:38:35 +0100
Subject: [PATCH 13/14] Update verb
---
src/pages/blog/2026-08-14-true-nullability.mdx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 7ed2126f42..3d54f61d9d 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -217,13 +217,13 @@ This is the traditional error propagation behaviour that we all know and...
Setting `onError: "PROPAGATE"` will be equivalent to the error behaviour in the
initial 2015 GraphQL Specification: error propagation/null bubbling.
-### `onError: "ABORT"`
+### `onError: "HALT"`
Ad-hoc scripts and similar clients throw away entire responses if any error
occurs, but currently the server still computes the "partial success" response
anyway. This mode allows clients to indicate that if any error occurs, they
won't read the data: any error should result in a
-`{ data: null, errors: [...] }` response, allowing the service to abort
+`{ data: null, errors: [...] }` response, allowing the service to halt
execution when the first error occurs.
### `onError: "NULL"`
@@ -302,7 +302,7 @@ error because the error propagates to the nearest nullable position. Not so with
If a GraphQL service can guarantee it will never need to perform error
propagation (for example, by requiring all clients to include `onError: "NULL"`
-or `onError: "ABORT"` in requests), then the schema can safely indicate to
+or `onError: "HALT"` in requests), then the schema can safely indicate to
clients the true intended nullability of a field in the traditional way:
with a `!`:
From 4835f91a5e883afbe987b8af64da959aff961ab0 Mon Sep 17 00:00:00 2001
From: Benjie Gillam
Date: Mon, 3 Aug 2026 16:20:20 +0100
Subject: [PATCH 14/14] Edited to reflect newer spec edits
---
.../blog/2026-08-14-true-nullability.mdx | 66 ++++++++++++-------
1 file changed, 42 insertions(+), 24 deletions(-)
diff --git a/src/pages/blog/2026-08-14-true-nullability.mdx b/src/pages/blog/2026-08-14-true-nullability.mdx
index 3d54f61d9d..6d30fe3c2e 100644
--- a/src/pages/blog/2026-08-14-true-nullability.mdx
+++ b/src/pages/blog/2026-08-14-true-nullability.mdx
@@ -235,8 +235,10 @@ This is what we're excited about!
`onError: "NULL"` completely disables error propagation within the GraphQL
service. From an error perspective, every position in the response (fields and
-lists alike) is an error boundary, as though it were nullable. This effectively
-changes the "non-nullable" type modifier to mean "null only on error".
+lists alike) is an error boundary, as though it were nullable (for
+error-handling only). This effectively changes the "non-nullable" type modifier
+to mean "null only on error" or, equivalently, "not null unless an error
+occurred".
Clients that opt in to this behaviour take responsibility for interpreting the
response as a whole, correlating the `data` and `errors` properties of the
@@ -259,31 +261,47 @@ the storage and retrieval of fetched GraphQL data. These clients are well
positioned to handle the responsibilities outlined above.
By disabling error propagation, these clients will be able to safely update
-their stores (including normalised stores) even when errors occur. They can also
-re-implement traditional GraphQL error propagation on top of these new
-foundations, shielding application developers from needing to learn this new
-behaviour (whilst still allowing them to reap the benefits!). They can even take
-on advanced behaviours, such as throwing the error when the application
-developer attempts to read from an errored field, allowing the developer to
-handle errors with their system's native error boundaries: `try/catch`,
-`raise/except`, or ``.
+their stores (including normalised stores) even when errors occur. Having fixed
+that underlying issue, they can then expose the data to the application either
+by reproducing the traditional error-propagation behavior locally (in which
+application code will not know that anything has changed), or by giving the
+application a more ergonomic error-handling infrastructure, by becoming an
+"error-handling client".
### "Error-handling clients"
-An error-handling client is a client that ensures that an "error null" can never
-be read by application code. A client that throws if an `errors` property exists
-in a response is an error-handling client; the `data` can never be read, so no
-"error nulls" can be read. Clients that implement error-handling behaviours such
-as throw-on-error at the field or fragment level also prevent application code
-from reading "error nulls" and are therefore also error-handling clients.
-
-Many clients, including `window.fetch()`, Apollo Client, URQL, and graffle, can
-be made into error-handling clients by integrating something like
-[`graphql-toe`](https://www.npmjs.com/package/graphql-toe) (Throw On Error): a
-0.5kB library that can be added to JavaScript projects and uses accessors so
-that, when application code attempts to read from an errored field, that error
-is thrown and can be processed by traditional error handling (`try/catch` or
-``).
+**An error-handling client is a client that ensures that an "error null" can never
+be read by application code.**
+
+The simplest form of an error-handling client already exists: it's a client that
+throws (or otherwise prevents reading `data`) when the `errors` property exists
+in the response. Since `data` can never be read, no "error nulls" can be read.
+
+For serious applications, more robust error handling would be desired which
+allows the application to make the most use of GraphQL's "partial success" aim.
+So long as the client does not allow an "error null" to be read, it's an error
+handling client. There's a number of techniques to achieve this, and you could
+even use a combination:
+
+- throw on error: when an "error null" is to be accessed, throw an error
+ instead so that it can be handled with traditional exception handling
+ behaviors such as `raise/except`, `try/catch`, or ``. A
+ simple implementation of this can be found in
+ [graphql-toe](https://github.com/graphile/graphql-toe/blob/main/src/index.ts)
+ which can add this behavior to Apollo Client, URQL, graffle and even
+ `window.fetch()` in just 512 bytes of gzipped code.
+- `@throwOnFieldError`: add this to a fragment in Relay, and when you attempt to
+ read the fragment if any of the fields errored then the fragment will throw
+- result types: when reading a fragment, return an `Result` type so
+ if an error occured in the fragment you cannot read the data
+- `@catch` to coerce errors into other values or behaviors
+- maybe your new innovative idea?
+
+By disabling error propagation and performing these behaviors at the field or
+fragment level, sibling fields no longer cause errors to impact unrelated areas
+of the application. By exposing errors ergonomically, we can ensure it's easy
+for developers to handle errors, rather than ignoring them and hoping for the
+best.
## True nullability