Which edits to a GraphQL schema break existing clients, and which do not?
answer
- Ask who exactly is broken
- Two failures wear the same word
- Does the old document still validate?
- Does the old client code still cope?
- Removals, renames, new requirements, narrowings
basics
~20 sRemoving or renaming a field, argument, type or enum value breaks clients, as does adding an argument or input field that is non-null with no default. Adding a new nullable field or optional argument breaks nobody.
solid answer
~50 sSeparate two questions. **Does every document that used to validate still validate?** Removing or renaming a field, argument, type or enum value breaks that, as does adding an argument or input-object field whose type is non-null with no default value, or changing a field from a leaf type to an object type. Those failures are loud: validation rejects the whole request before execution, so the caller gets `errors` and no data. **Does client code that used to handle a response still handle it?** That is a different list — adding a value to an output enum leaves every document valid while handing a reader a value it has never seen. In a clinical-trial registry, renaming `Trial.principalInvestigator` breaks every deployed document that selects it even though the data behind it is unchanged. Additive edits — a new nullable field, a new optional argument — break neither list.
code
graphql · 11 lines# before
type Trial {
registryId: ID!
principalInvestigator: String!
}
# after: both edits invalidate deployed documents
type Trial {
trialRegistryId: ID!
leadInvestigator: String!
}go deeper
Be ready to name the obvious breakers on the spot: removing a field, renaming a field, removing an argument or enum value, adding a required argument. Say clearly that a rename is a removal plus an addition, not a cosmetic edit.
Explain the mechanism, not just the list: validation runs before execution, so an invalid document fails as a whole request with no data. Be able to sort an edit into 'breaks documents' versus 'breaks client code'.
Show that you check breakage against real callers and their deploy cadence, not against the schema in the abstract, and that you know an enum addition can be an incident even though every document stays valid.
Own the framing question: breaking is relative to a consumer, so the interesting decision is which parts of the graph get which compatibility regime and who pays when a rule is relaxed.
## "Breaking" is two questions wearing one word Before cataloguing anything, split the failure in two, because the two halves have different victims, different symptoms and different fixes. **Question one: does every document that used to validate still validate?** A GraphQL caller sends an executable document that names fields, arguments and type conditions. The server checks that document against the schema *before* it executes anything. If the schema no longer contains something the document names, the whole request is rejected: the response carries an `errors` entry and no executed data at all. This failure is loud and immediate, and it hits a client that has not changed a line of code, because the client did not have to change — the schema did. **Question two: does client code that used to handle a response still handle it?** Here the document may remain perfectly valid while the *values* coming back are ones the caller has never seen. Nothing on the server looks wrong: validation passes, execution succeeds, latency is normal. The failure lives entirely inside the caller, which is why it is usually found by a user rather than by a dashboard. Nearly every disagreement about whether an edit is "breaking" is really a disagreement about which of these two it trips. ## The edits that break documents Take a clinical-trial registry graph with `Trial`, `Site`, `Participant`, `Enrollment` and `AdverseEvent`. * **Removing a field.** Deleting `Trial.registryId` invalidates every deployed document that selects it. * **Renaming a field.** `Trial.principalInvestigator` becoming `Trial.leadInvestigator` is a removal plus an addition. The data behind it is identical, the old name is gone, and old documents fail. "The data didn't change" is not a defence; the name *is* the contract. * **Removing or renaming an argument.** Turning `trials(phase:)` into `trials(trialPhase:)` breaks every caller that passed the old name. * **Adding a required argument or a required input-object field.** An argument whose type is non-null and which declares no default value must be supplied. Add one to an existing field and every document selecting that field becomes invalid — including documents belonging to teams with no interest in the new argument. * **Changing a field's type incompatibly.** `Site.address: String` becoming `address: Address` breaks documents outright, because an object-typed field requires a sub-selection and a leaf field forbids one. `AdverseEvent.severity: String` becoming an enum keeps documents valid — both are leaf types — but changes what may be *sent*: a quoted `"MILD"` is not a valid enum literal in a document, though the same value passed through a variable still coerces, since enum values travel as JSON strings. * **Removing a type, or removing an interface from a type's `implements` list.** A document carrying a type condition on the vanished name no longer validates, and a caller that branches on the `__typename` meta-field starts seeing a string it does not recognise. * **Removing an enum value.** Every document or variable value that *sends* it is now rejected. * **Widening an output type back to nullable.** `visits: [Visit!]!` becoming `[Visit!]` keeps the document valid but hands readers a null they never had to consider. ## The edits that break code, not documents * **Adding a value to an enum that appears in output position.** Every document still validates; a reader that maps each known value to a behaviour now meets one it cannot map. * **Adding a field to an object type.** Safe for readers — nobody selects it yet. The exception is structural rather than client-facing: a field added to an *interface* must be added to every implementing type, or the schema itself no longer builds. * **Strengthening a nullable output field to non-null.** Safe for readers, who were already coping with null. ## Why GraphQL makes this unusually tractable Two properties help. The schema is a machine-readable artefact, so the difference between two versions is computable rather than a matter of reading release notes. And callers declare their selections up front, so in principle a server can say exactly which documents an edit would invalidate — *if* it has the documents. That conditional is the whole difficulty of a public endpoint that accepts arbitrary documents: the schema owner can enumerate what an edit breaks in theory but not who is doing it. ## The traps Do not confuse "no client uses it" with "no client uses it *today*, on the versions I can see". And do not assume a removal becomes safe because it was announced; announcing a removal changes who is surprised, not what validation does with the old document.
- Is renaming an object type breaking for a client whose documents never write that type name?It can be. The `__typename` meta-field is available on every object, interface and union selection and returns the concrete type name, so any client branching on that string stops matching the moment the type is renamed. Documents that do carry a type condition on the old name fail validation outright. A type rename is never a purely internal edit.
- Where does a client discover that a field it selects has been removed?In validation, which runs before execution. The server rejects the whole request: the response carries an `errors` entry naming the unknown field, and no data is executed at all. There is no partial response and no null placeholder — the other twenty healthy fields in that document do not resolve either.
- Is adding a field to a type ever a breaking change?For readers, essentially never: nobody selects a field that did not exist. The exception is structural. A field added to an interface must also be added to every type implementing it, or the schema no longer builds — so the edit is breaking for the schema author rather than for any caller.
saying these in an interview costs you the question
- Claims nothing breaks because clients pick their own fields
- Thinks a removed field comes back as null
- Calls a rename safe because the data is unchanged
- Assumes every addition to a schema is safe
- Believes breakage appears only when a client redeploys
- Expects partial data when one selected field is invalid