Skip to main content

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

  1. Additive change ships. New field lands alongside the old one. Additive is always safe — this is the whole point of GraphQL.
  2. 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.

  1. 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.
  2. 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 Float that becomes a String "on the same field" breaks every client simultaneously — that's a version bump wearing a trench coat.
  • Never widen nullability on live fields. String!String breaks 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.