Skip to content

How keys are built

GraphQL only For plain HTTP, name keys in the Surrogate-Key header on your origin's response.

The directive says which fields form a key. This page is what GraphPilot does with the values it finds in a response, and what happens when a directive cannot produce a key at all.

  • A deep key includes its full path. @surrogateKey(of: "category.id") produces Product:category.id:<id>. Each segment must be a valid field, and the last one must not be an object, interface or union.
  • An enum contributes its value as it appears in the response, for example Product:stockStatus:SOLD_OUT.
  • A list of scalars or enums contributes one key per distinct element, not one key holding the whole list: tags: ["new", "bestseller"] yields Product:tags:new and Product:tags:bestseller. The order of the elements does not matter, and an element that appears twice yields its key once. In a composite key, a list multiplies out into one key per combination, as described under composite keys.
  • A list of objects contributes a key for each object, based on the key fields.
  • Keys are collected in a set. A key that two directives, or two objects, both produce is attached once, so defining the same key twice costs nothing at runtime.
  • Field names in a composite key are sorted alphabetically, and casing is never changed. The only other normalization is the character folding, which leaves ordinary ASCII values untouched.
  • The key does not depend on the client’s aliases. See aliases.

A key is generated even when a field it names is null, with an empty value: @surrogateKey(of: "brand.id") on a product without a brand produces Product:brand.id:. That keeps caching and purging symmetric: a cached product without a brand can still be purged, an update from null to a value can purge both the old key (Product:brand.id:) and the new one (Product:brand.id:acme), and a purge of Product:brand.id: reaches every product without a brand.

A key on a field that returns an object is the exception: @surrogateKey(of: "id") on Category.featured produces no key at all when featured is null, because there is no object to key. A static key on that field is still attached.

A union member or implementor on which the path does not exist at all contributes no key; the empty value only appears when the field exists and is null. With @surrogateKey(of: "author.id") on a search field returning Book | Movie:

... on Book { author { id } } # author.id = "A1"
... on Movie { director { name } } # Movie has no author field

A book produces Query:search.author.id:A1; a movie produces no key from this directive. Add a second directive, such as @surrogateKey(of: "director.id"), to key movies as well.

When the fragments of a union or interface field use different aliases, GraphPilot reads the response path that matches each object:

query {
search { # Returns Union type
... on Book {
author { _gp_id: id } # Uses alias "_gp_id"
}
... on Magazine {
_gp_author: author { id } # Uses alias "_gp_author" for entire field
}
}
}

For @surrogateKey(of: "author.id"), a book is read at author._gp_id and yields Query:search.author.id:BA1; a magazine is read at _gp_author.id and yields Query:search.author.id:MA1. The key is the same whichever alias the client used, so a purge reaches the response either way.

A directive the deployment build refuses contributes no key, and the deployment still succeeds. Every other key in the schema still applies, including a static value on the same directive, so the first thing you notice without reading the diagnostics is a response that never invalidates.

The build refuses, with the type, the directive, and its line and column:

  • an of naming a field that does not exist, or a path that does not end in a scalar, enum, or list thereof;
  • an argument of the wrong type;
  • a bare @surrogateKey on a type that needs a selection;
  • a directive written where no key can come from, such as inside extend type or on a field argument.

Read the diagnostics of your deployment. The portal shows them under Schema → Findings for the live deployment and on each deployment’s page under Deployments. gpilot deploy prints them when the deployment finishes, and the GraphPilot API returns them too. Only the deployment build validates your schema, so read its diagnostics.

Check them whenever you change a type that carries a key. If a field used in an of argument is removed or renamed, the next deployment reports it and the directive produces no key from then on. Keys already in the cache are not affected until they are invalidated or expire. To see the keys your directives produced for real data, read the gp-surrogate-key header.