Versioning & deprecation without breaking anyone
GraphQL's promise is a versionless API. That promise is real — but only if you run deprecation as a process, not a comment.
The lifecycle we run
- Additive change ships. New field lands alongside the old one. Additive is always safe — this is the whole point of GraphQL.
- Old field is deprecated, with a destination:
type Product {
price: Float! @deprecated(reason: "Use `pricing.current` — money as Float loses precision. Removal: 2026-Q4.")
pricing: Pricing!
}
A deprecation without a migration path and a date is just documentation debt.
- Usage is measured. Your router/gateway tells you exactly which clients query which fields. Zero-usage fields for N days → removable. Non-zero usage → you have a named list of teams to chase, not a broadcast email and a prayer.
- Removal is enforced in CI. Schema checks block removals of fields with live traffic. Humans forget; composition checks don't.
Hard rules
- Never change a field's type or semantics in place. Add a sibling, deprecate, migrate, remove. A
Floatthat becomes aString"on the same field" breaks every client simultaneously — that's a version bump wearing a trench coat. - Never widen nullability on live fields.
String!→Stringbreaks generated client code (Kotlin/Swift/TS codegen treats it as a type change). Treat nullability changes as breaking, both directions. - Enums: additions are breaking for careless clients. Publish (and test) that clients must handle unknown enum values; add an
@oneOf-style fallback story before your first enum grows.
Persisted queries change the game
Once you run persisted/trusted operations only, you know the exact set of operations in production. Deprecation stops being probabilistic:
- The registry tells you which persisted operations touch a deprecated field.
- Removal becomes a mechanical "these 3 operations from these 2 clients" conversation.
- Bonus: you've also closed arbitrary-query DoS and locked your attack surface.
If you operate a public graph and can't do persisted-only, you'll live with usage metrics and generous sunset windows — 6 months minimum for anything with external consumers.