|
| 1 | +--- |
| 2 | +title: Disabling Error Propagation |
| 3 | +sidebarTitle: Disabling Error Propagation |
| 4 | +--- |
| 5 | + |
| 6 | +# Disabling Error Propagation |
| 7 | + |
| 8 | +GraphQL's traditional execution behavior propagates an execution error from a |
| 9 | +non-null field to the nearest nullable parent. This preserves the schema's |
| 10 | +strict non-null guarantee, but it can also destroy useful data from the response |
| 11 | +and make the result unsafe for normalized client caches. |
| 12 | + |
| 13 | +GraphQL.js v17 exposes an experimental operation directive for services and |
| 14 | +clients that want to test the non-propagating error mode: |
| 15 | + |
| 16 | +```graphql |
| 17 | +directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION |
| 18 | +``` |
| 19 | + |
| 20 | +When an operation uses this directive, an execution error still appears in the |
| 21 | +`errors` list with its normal `path`, but the errored response position becomes |
| 22 | +`null` directly. The error no longer nulls out non-null parents. |
| 23 | + |
| 24 | +## Motivation |
| 25 | + |
| 26 | +The current |
| 27 | +[error behavior](https://github.com/graphql/graphql-spec/pull/1163) proposal |
| 28 | +describes error propagation as the source of two practical problems: it removes |
| 29 | +data that could otherwise be used, and it makes errored responses unsafe to |
| 30 | +write into normalized stores. The broader |
| 31 | +[Semantic Nullability RFC](https://github.com/graphql/graphql-wg/blob/main/rfcs/SemanticNullability.md) |
| 32 | +also traces the consequences through partial success, schema design, and |
| 33 | +normalized caching, and the Nullability WG captured the cache issue as |
| 34 | +[normalized-cache corruption from null bubbling](https://github.com/graphql/nullability-wg/issues/20). |
| 35 | + |
| 36 | +The motivations are: |
| 37 | + |
| 38 | +- Preserve useful partial data. A failed field should not necessarily remove |
| 39 | + sibling response data that resolved successfully. |
| 40 | +- Keep normalized caches safe. A bubbled ancestor `null` can look like a real |
| 41 | + object update when only one descendant field failed. |
| 42 | +- Separate execution errors from true semantic nullability. Schema nullability |
| 43 | + should be able to express whether `null` is a normal domain value, not whether |
| 44 | + a resolver might fail. |
| 45 | + |
| 46 | +The core issue behind those motivations is that traditional error propagation |
| 47 | +couples two concerns: |
| 48 | + |
| 49 | +- Whether `null` is a meaningful value for a field. |
| 50 | +- Whether resolving that field might fail. |
| 51 | + |
| 52 | +Because any field can fail, schema authors often make fields nullable even when |
| 53 | +`null` is not a normal domain value. This avoids losing larger chunks of the |
| 54 | +response when a resolver fails, but it also creates a "nullable epidemic": |
| 55 | +clients and generated types must treat many semantically required values as |
| 56 | +nullable just because execution errors are possible. |
| 57 | + |
| 58 | +Error propagation also creates a cache-safety problem for clients with normalized |
| 59 | +stores. If `viewer.name` is already cached and a later query for `viewer.age` |
| 60 | +raises an execution error, traditional propagation may replace the whole |
| 61 | +`viewer` object with `null`. Writing that bubbled `null` can corrupt the |
| 62 | +normalized cache entry for `viewer`, even though the already-known `name` value |
| 63 | +is still valid. With propagation disabled, the client can keep the `viewer` |
| 64 | +object, record the error at `["viewer", "age"]`, and avoid turning a field error |
| 65 | +into an ancestor-object cache update. |
| 66 | + |
| 67 | +Disabling error propagation moves responsibility for execution-error handling to |
| 68 | +the client. Clients that read `errors` by path, throw on field errors, use error |
| 69 | +boundaries, or store errors alongside normalized data can keep the successful |
| 70 | +parts of the response without treating bubbled ancestor `null` values as real |
| 71 | +data. |
| 72 | + |
| 73 | +This also opens the path toward semantic nullability, but with an important |
| 74 | +distinction: disabling propagation does not make every raw `null` in `data` a |
| 75 | +semantic `null`. A field that raised an execution error is still represented as |
| 76 | +`null`; the `errors` entry at the same path tells the client that this is an |
| 77 | +error `null`. What changes is that descendant errors no longer replace ancestor |
| 78 | +positions, so a `null` at a position without a matching execution error can more |
| 79 | +directly reflect that field's semantic nullability. |
| 80 | + |
| 81 | +## Using it in GraphQL.js |
| 82 | + |
| 83 | +Define the directive in the schema while this feature is experimental: |
| 84 | + |
| 85 | +```graphql |
| 86 | +directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION |
| 87 | + |
| 88 | +type Query { |
| 89 | + viewer: User! |
| 90 | +} |
| 91 | + |
| 92 | +type User { |
| 93 | + id: ID! |
| 94 | + displayName: String! |
| 95 | + nickname: String |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +Apply the directive to an operation: |
| 100 | + |
| 101 | +```graphql |
| 102 | +query Profile @experimental_disableErrorPropagation { |
| 103 | + viewer { |
| 104 | + id |
| 105 | + displayName |
| 106 | + nickname |
| 107 | + } |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +If `displayName` throws, traditional propagation would null out `viewer`, and |
| 112 | +because `viewer` is non-null, the whole `data` entry could become `null`. With |
| 113 | +error propagation disabled, GraphQL.js reports the error at the field path and |
| 114 | +keeps the rest of the object: |
| 115 | + |
| 116 | +```json |
| 117 | +{ |
| 118 | + "data": { |
| 119 | + "viewer": { |
| 120 | + "id": "1", |
| 121 | + "displayName": null, |
| 122 | + "nickname": "Ada" |
| 123 | + } |
| 124 | + }, |
| 125 | + "errors": [ |
| 126 | + { |
| 127 | + "message": "Could not fetch display name.", |
| 128 | + "path": ["viewer", "displayName"] |
| 129 | + } |
| 130 | + ] |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +The directive changes execution behavior only for operations that use it. |
| 135 | +Operations without the directive keep traditional GraphQL error propagation. |
| 136 | + |
| 137 | +This directive only tests one behavior: error propagation is disabled for the |
| 138 | +operation. It is not the final shape of the broader standards proposal. |
| 139 | + |
| 140 | +## Relationship to `onError` |
| 141 | + |
| 142 | +The newer draft |
| 143 | +[`onError` GraphQL.js implementation](https://github.com/graphql/graphql-js/pull/4364) |
| 144 | +tracks the |
| 145 | +[error behavior](https://github.com/graphql/graphql-spec/pull/1163) spec |
| 146 | +proposal, which generalizes this idea into a client-selected `onError` behavior. |
| 147 | +Rather than only toggling propagation on or off, the proposal describes multiple |
| 148 | +modes, including: |
| 149 | + |
| 150 | +- Traditional propagation behavior. |
| 151 | +- Resolving errored positions to `null` without propagating to non-null parents. |
| 152 | +- Halting execution on the first execution error. |
| 153 | + |
| 154 | +The current `@experimental_disableErrorPropagation` directive lets GraphQL.js |
| 155 | +users experiment with the second mode. Clients can use it to test error handling |
| 156 | +by path, normalized-cache behavior, and UI error boundaries before the request |
| 157 | +shape is standardized. |
| 158 | + |
| 159 | +The `onError` proposal relies on |
| 160 | +[Service capabilities](https://github.com/graphql/graphql-spec/pull/1208) |
| 161 | +because clients need a way to discover support and defaults before choosing a |
| 162 | +mode automatically. A client cannot safely assume that a service understands a |
| 163 | +new request parameter, supports every proposed mode, or uses the same default |
| 164 | +error behavior. Capabilities give tools and clients an in-band way to learn what |
| 165 | +the service supports and how to configure themselves without out-of-band |
| 166 | +instructions. |
| 167 | + |
| 168 | +## Service capabilities and introspection |
| 169 | + |
| 170 | +Service capabilities are proposed as an introspection feature. They were |
| 171 | +originally part of the error-behavior work and were later extracted into their |
| 172 | +own [Service capabilities](https://github.com/graphql/graphql-spec/pull/1208) |
| 173 | +proposal. Existing GraphQL introspection describes the schema's type system: |
| 174 | +object types, fields, arguments, directives, and related schema metadata. Service |
| 175 | +capabilities extend that idea to service-level behavior that is outside the type |
| 176 | +system, such as which experimental syntax, transport features, or error |
| 177 | +behaviors the service supports. |
| 178 | + |
| 179 | +The proposal introduces a service introspection entry point, currently described |
| 180 | +as a `__service` meta-field, that returns capability records. For error |
| 181 | +behavior, those records let a client discover whether the service supports |
| 182 | +client-selected `onError` behavior, which modes are accepted, and what default |
| 183 | +mode applies when the request does not choose one. |
| 184 | + |
| 185 | +That distinction matters for clients. A schema can contain the same fields and |
| 186 | +types whether it uses traditional error propagation or a non-propagating error |
| 187 | +mode, so ordinary type introspection is not enough to configure the client |
| 188 | +safely. Capabilities make that operational contract introspectable. |
| 189 | + |
| 190 | +## Proposal history |
| 191 | + |
| 192 | +This area has gone through several proposals. The broader |
| 193 | +[Semantic Nullability RFC](https://github.com/graphql/graphql-wg/blob/main/rfcs/SemanticNullability.md) |
| 194 | +collects the problem history and solution matrix. |
| 195 | + |
| 196 | +The latest proposals to watch are: |
| 197 | + |
| 198 | +- [Error behavior](https://github.com/graphql/graphql-spec/pull/1163) defines |
| 199 | + the client-selected `onError` modes. GraphQL.js v17 makes one part of this |
| 200 | + current proposal available experimentally through |
| 201 | + `@experimental_disableErrorPropagation`: the mode where errored positions |
| 202 | + resolve to `null` without propagating through non-null parents. |
| 203 | +- [Service capabilities](https://github.com/graphql/graphql-spec/pull/1208) |
| 204 | + is the capabilities work extracted from the error-behavior proposal. It |
| 205 | + defines the introspection mechanism for advertising service-level features and |
| 206 | + configuration. It is related to error behavior because clients need to |
| 207 | + discover whether `onError` is supported, which modes may be requested, and |
| 208 | + which behavior applies when no mode is requested. |
| 209 | + |
| 210 | +## What is next |
| 211 | + |
| 212 | +The current GraphQL.js directive is intentionally experimental and uses an |
| 213 | +implementation-prefixed name. Future standards work is expected to settle the |
| 214 | +request shape and discovery story separately: |
| 215 | + |
| 216 | +- The error-behavior proposal defines the execution modes. |
| 217 | +- Semantic nullability work defines how schemas and clients distinguish values |
| 218 | + that are nullable in normal business logic from values that are only `null` |
| 219 | + because an execution error occurred. |
| 220 | +- The service-capabilities proposal defines how clients discover whether those |
| 221 | + modes are supported and what the default behavior is. |
| 222 | + |
| 223 | +Until that settles, clients should only use |
| 224 | +`@experimental_disableErrorPropagation` with services that document support for |
| 225 | +it. When it is enabled, clients should treat `null` at an errored path as an |
| 226 | +error `null`, not as a semantic `null`. |
0 commit comments