What entries can a single item in a GraphQL response's errors list carry, and which is required?
answer
- One entry is mandatory, three optional
- Prose for humans, structure for programs
- One points at text, one at the response
- Locations count from one, list positions from zero
- extensions is the only sanctioned extra key
basics
~20 sOnly message is required, a string describing the failure. An error may also carry locations pointing into the document sent, path locating the field in the response, and extensions, a free-form map for server-specific data such as a code.
solid answer
~50 sEach item in `errors` is a map with a small fixed vocabulary. **`message`** is the only unconditionally required entry: a human-readable string, with no structure the specification cares about. **`locations`** is optional — a list of `{ line, column }` maps, both counting from 1, marking where in the document the caller sent the relevant syntax element begins. **`path`** is required whenever the error can be tied to a particular field of the result: a list running from the root of `data` to that field, using response keys as strings and **0-indexed** integers for positions inside lists. **`extensions`** is optional, must be a map, and is the one slot the specification leaves entirely to the server — error codes, correlation ids, timing. The specification also asks services not to add any other top-level entry to an error, reserving that space for future editions, which is precisely why `extensions` exists.
code
json · 11 lines{
"data": { "warehouse": { "bins": [ { "stockItems": [ null ] } ] } },
"errors": [
{
"message": "Forecast service timed out",
"locations": [ { "line": 6, "column": 11 } ],
"path": [ "warehouse", "bins", 4, "stockItems", 11, "reorderPoint" ],
"extensions": { "code": "UPSTREAM_TIMEOUT", "traceId": "8f21c4" }
}
]
}go deeper
Be able to name the four entries and say which one is mandatory. If you remember nothing else, remember that message is required prose and extensions is where anything server-specific lives.
Explain what each entry points at: locations at the document text sent, path at a position in the response, extensions at whatever the server chose to attach. State the 1-based versus 0-based difference without hedging.
Show you know which entries are safe to build behaviour on. Codes in extensions and paths are stable and machine-readable; message text is not, and treating it as an interface is how recovery logic silently stops running.
Own the error format as a cross-team contract: one code vocabulary, a documented meaning per code, additive evolution only, and a shared mapping layer so every service in the graph emits the same shape.
## What this is about A GraphQL response is a map. When something went wrong, that map carries an `errors` entry holding a **list**, and each item in that list is itself a small map describing one failure. The response envelope — which top-level keys exist, when `data` is absent — is a separate concern. This is about the shape of **one item inside the list**, and its vocabulary is deliberately tiny: `message`, `locations`, `path`, `extensions`. ## message — the one entry that is always required Every error **must** carry `message`, a string describing the failure. The specification says nothing about its structure, its language, or its wording. It is prose written for a human being who is debugging, and that is the whole of its contract. The consequence matters more than the rule: because nothing constrains the text, nothing stops it changing. A server upgrade rewords it, a translation layer localises it, a hardening pass replaces it wholesale with something bland. Any program that branches on it is depending on an unversioned string. Log it, show it to a developer, put it in a bug report — never write `if (message.includes(...))`. ## locations — where in the document the caller wrote it Optional. When the error can be tied to a point in the document that was sent, the error **should** carry `locations`: a list of maps, each holding `line` and `column`, both **positive integers starting at 1**, marking the beginning of the associated syntax element. It is a list because a single error can implicate more than one place in the document. Note what it points at: the **text the client sent**, not the schema file and not the response. It answers "where did you write this?", never "where in the data did it break?". ## path — where in the response the failure landed When an error can be associated with a particular field of the result, the error **must** carry `path`. It is a list of segments running from the root of `data` down to that field. Segments that name fields are strings — the **response keys**, so an aliased field appears under its alias — and segments that name a position inside a list field are **0-indexed integers**. The asymmetry is a favourite interview trap: `locations` counts lines and columns from **1**; `path` counts list positions from **0**. They are different coordinate systems over different artefacts, and there is no reason for them to agree. `path` is what makes a partial response usable at all. Without it, a caller holding a body full of `null`s cannot tell a value that is legitimately absent from a hole left by a failure. ## extensions — the one sanctioned free slot A service **may** add an `extensions` entry. If present its value must be a map, and the specification then imposes nothing whatsoever on its contents. This is the designated home for everything the format does not model: a machine-readable code, a correlation id, a timestamp, a retry hint, timing data. Two things candidates routinely get wrong here. First, the `code` key inside `extensions` is a **widespread convention among server implementations, not a specified key** — the spec knows about `extensions` and nothing about what goes in it. Second, the per-error `extensions` map is not the response's own top-level `extensions` entry; they are different maps at different levels, and a client reading the wrong one finds nothing. ## Why you may not invent a fifth top-level entry The specification tells services **not** to add other entries directly to an error map, because that namespace is reserved for future editions of the specification to grow into. So a bespoke `severity` sitting beside `message` is out of contract; `extensions.severity` is entirely fine. That single rule is why `extensions` exists at all. ## Worked example A warehouse inventory graph. `StockItem` is one of those types that accreted for years — 37 fields, one of which, `reorderPoint`, is computed by a forecasting service that has just timed out for the item in list position 11: ```json { "data": { "warehouse": { "bins": [ { "stockItems": [ null ] } ] } }, "errors": [ { "message": "Forecast service timed out", "locations": [ { "line": 6, "column": 11 } ], "path": [ "warehouse", "bins", 4, "stockItems", 11, "reorderPoint" ], "extensions": { "code": "UPSTREAM_TIMEOUT", "traceId": "8f21c4" } } ] } ``` Read it entry by entry. `message` tells the on-call engineer what happened. `locations` says the offending field was written on line 6, column 11 of the document that was sent. `path` says the failure belongs to `reorderPoint` of the twelfth stock item (index 11) inside the fifth bin (index 4). `extensions.code` is the only part of the whole error a client should branch on, and it is there because this server chose to put it there. ## Why one error map, and why a list GraphQL is transport-independent, so an error cannot be an HTTP status; the response has to describe the failure itself. And execution does not stop at the first bad field — sibling fields keep resolving — so one response can legitimately carry several errors at once. A list of self-describing maps is the smallest structure that supports both facts.
- A server wants to mark some errors as retryable. Where does that flag belong?Inside `extensions`, as a key of that map — for example `"extensions": { "code": "UPSTREAM_TIMEOUT", "retryable": true }`. It must not go beside `message` as a new top-level entry of the error: the specification reserves that namespace for future editions and tells services not to add to it. `extensions` exists exactly so servers can carry data the format does not model, and it has no constraints on its contents.
- Is the code key inside extensions part of the GraphQL specification?No. The specification defines `extensions` and then says explicitly that it places no restrictions on its contents. A string `code` is a very widespread convention across server implementations and clients, which is why it feels standard, but nothing validates it, nothing reserves the key, and two servers may use entirely different vocabularies. Treat the code set as your own published contract, not as something the specification guarantees.
- Why is message a poor thing for a client to branch on?Because nothing in the format constrains it. It is prose intended for a developer, so its wording can change in a server upgrade, be localised, or be replaced with something deliberately vague. A client matching on substrings is coupled to an unversioned string that no one will think to treat as a breaking change. Branch on a code in `extensions` and use `message` for logs and developer-facing display only.
saying these in an interview costs you the question
- Claiming every error must include a path entry
- Thinking code is a specified top-level key of an error
- Adding custom keys beside message instead of inside extensions
- Branching on the message string in client code
- Saying locations points into the schema or the response
- Assuming an errors entry means data must be absent