Skip to main content

Add Transitional Non-Null appendix (`@noPropagate` directive)

At a glance​

Timeline​


This is essentially solution 8 to the Semantic Nullability RFC:

  • Enables semantic nullability to be reflected in schemas without breaking legacy behavior.
  • Facilitates incremental adoption of modern error handling without requiring disruptive changes.
  • Requires minimal spec impact and is fully optional for implementations.
  • Reflects transitional nature of this change in behavior

I've based it on:

  • #1163

since, like all solutions to the semantic nullability problem1, it is designed to enable clients with error propagation disabled to leverage the true nullability of the underlying data without breaking legacy clients. The approach could be rebuilt atop an alternative method of toggling error propagation, for example a directive-based approach.


This PR introduces an appendix to the GraphQL specification defining an optional solution to the semantic nullability problem using the following key mechanisms:

  • @noPropagate2 directive — allows schema authors to annotate Non-Null return types as transitional, suppressing propagation but preserving runtime error generation.

  • Transitional Non-Null semantics — errors at these positions behave like nullable fields in terms of (no!) propagation but like non-nullable fields in value completion (error on null).

  • New __Field.noPropagateLevels: [Int!] field — exposes transitional status to modern clients.

  • Transitional non-null hidden from legacy clients — tooling using the legacy PROPAGATE error behavior will get results from __Field.type that unwrap transitional non-null types.3

This solution attempts to address all of the feedback on previous solutions to this problem, whilst being explicitly transitional. It:

  • Is optional: explicitly only for schemas supporting legacy clients

  • Requires no changes to the main spec text

  • Introduces no new syntax

  • True to its name: an error here will not propagate (@noPropagate), regardless of whether error propagation is enabled or disabled.

  • Maintains introspection results for existing (deployed) clients and tooling

  • Maintains error boundaries for existing (deployed) clients

  • Allows all new schemas and new fields to use ! (non-null) directly for semantically non-null positions

  • Allows existing fields to use ! (non-null) for error handling clients without breaking legacy clients by adding the @noPropagate directive4

  • Can be adopted gradually, field-by-field, or en masse by applying @noPropagate to all nullable positions.

  • Can be removed from each field the moment no legacy clients query it

Footnotes​

  1. Except solution 5 ↩

  2. This is essentially the same as the @semanticNonNull directive, but more strictly defined and reflected through introspection. ↩

  3. This may be controversial, but I truly think it's the right decision. All new tooling (and all new clients!) should use onError: ABORT or onError: NO_PROPAGATE, and thus will see the true introspection. Existing tooling doesn't know about onError and so should not see these "transitional" non-null types. ↩

  4. And if you forget to add it, adding it later is only a potentially breaking change for any new versions of legacy clients deployed since the change; error-handling clients (NO_PROPAGATE or ABORT) are unimpacted. ↩