Schema-driven GraphQL edge

GraphQL Edge Cachingreduces your costs. needs no code changes. makes your app faster. understands your schema. is never out of date. turns spikes into hits. fits federated teams. clears only what changed.

Drop GraphPilot in front of your GraphQL API. Repeat queries answer from the edge, and every cache clears the moment your data changes.

Works with Apollo, Wundergraph, Hasura and any other GraphQL server. Free to start, no credit card required.

Response sizeA single record
Typical GraphQL API0 ms
With GraphPilot0 ms
0× faster

p95 cache-hit latency

The problem

Caching GraphQL is easy.

Keeping it correct isn't.

Put a normal CDN in front of GraphQL and three problems surface at once.

One URL

A CDN sees one blind endpoint

Every query and mutation shares one URL, with the operation and variables buried in the request. A CDN can't tell them apart, so read-heavy APIs hammer the origin all day.

Stale data

A clock can't tell what changed

Short TTLs throw away the hit rate; long TTLs serve stale data. A clock can't say which mutation changed which result, so you either under-cache or risk correctness.

1 → N caches

Invalidation is a distributed system

A single write invalidates entities, lists and derived results across many cached responses. Hand-written purge rules are fragile and rarely stay correct.

cache performance · demo benchmark
up to0%
fewer origin requests
120 ms
p95 cache-hit latency
0%
cache hit rate
<600 ms
to purge worldwide
Why we built it

There's already one.

So why another one?

We set out to cache a GraphQL API at the edge and kept hitting the same wall: caching was never the hard part, keeping it correct was. So we built the layer we wanted.

Where we go further

See how it caches, right in the schema.

schema.graphql

the schema you write

type Product @cacheRule(maxAge: 300) {
id: ID! @surrogateKey
name: String!
price: Float!
}

the query, keyed either way

# a client omits the key
{ product(id: "42") { name } }
# GraphPilot injects it at the edge
{ product(id: "42") { id name } }

The backend team owns it

Cache and invalidation are directives next to the fields they govern, so the team that owns a type owns how it caches. No central config to coordinate.

And can rely on it

Forget the id in a query and caching still holds: GraphPilot injects it, so every cached response stays invalidatable.

  • Built for federated graphs

    In front of any gateway, we cache a federated graph as one. First-class, not an afterthought.

  • Empty lists stay correct

    An empty result is a real cache entry, cleared the moment a write could fill it.

  • Keys that never drop

    Cache tags persist across invalidations, so nothing quietly goes stale.

  • Drop it in, keep your stack

    No new gateway, no client changes. It sits in front of the API you already run.

  • Invalidation you can verify

    See exactly which cache tags a mutation clears. Not a black box.

  • EU-first and GDPR-native

    Built in Germany to EU standards. GDPR compliance by design, not a bolt-on.

How it works

One layer in front of your API.

Here's how it earns its keep.

GraphPilot is a GraphQL-native proxy, written in Rust. It sits in front of your API and caches it for you, reading your schema to decide what to cache, how long to hold it, and what to clear when your data changes.

Client

Web · Mobile · Server

Request
Response
GraphPilot
Request
Response

Origin

GraphQL · REST · HTTP

Cache hits answer at the edge. Only misses and writes reach your origin.

Never stale, no black box

Mutations invalidate

automatically.

Change your data and GraphPilot refreshes every response that touched it, and shows you exactly which cache tags it cleared. When a mutation doesn't reveal everything it changed, the behaviours below close the gap, so nothing is left stale.

# changes a product's price
mutation UpdatePrice {
updateProduct(id: "42") {
price
}
}
every response fresh

Cached responses

The mutation only invalidates the entries it touches; each refills on its next request, not from the mutation.

When the response says nothing at all

Some mutations hide

what they changed.

GraphPilot reads a mutation's response to know which entities to clear. But some mutations return only success: true and reveal nothing. Your origin can list the affected entities as cache tags in the response header, and GraphPilot purges exactly those. No schema change, no API call.

POST /graphql 200 OK
content-typeapplication/json
cache-controlpublic, max-age=300
age0
surrogate-keyProduct:42 Product:88 Category:sale
Purgedexact match · nothing else cleared
Product:42Product:88Category:sale
Programmatic invalidation

Changed elsewhere?

Purge via API.

When a change happens somewhere GraphPilot can't see, an event on your bus, a cron job, an admin action in another system, call the purge API with the keys to clear. Full, precise control whenever you need it.

Event-based

Purge from your event bus the moment upstream data changes, with no GraphQL mutation involved.

Scheduled

Expire a set of keys on a cron or a job when a batch import or recompute finishes.

Cross-system

An admin action in another service clears exactly the right cached reads.

Precise or broad

Purge exact cache tags, or clear everything of a type by typename.

api.graphpilot.io/purge

a change from your system

Kafka eventcalls the purge API
POST https://api.graphpilot.io/purge
{ "keys": ["product:42"] }
200purge accepted
No missed invalidations

Your clients forget the id?

GraphPilot doesn't.

When a query skips a key field, most caches still store the response but can't clear it when the data changes. GraphPilot injects the keys your schema needs, so every response stays precisely invalidatable, one entity or a whole group.

The query your client sends

injected by GraphPilot
query {
product(id: "42") {
id
sku
name
reviews(first: 3) {
rating
}
}
}
Example surrogate keys GraphPilot generates
Product:id:42Product:sku:CH-042

One product, keyed by its id and its SKU, so either one invalidates it.

Spike-proof origin

A thousand requests,

one origin fetch.

When a spike sends the same query all at once, GraphPilot collapses them into a single origin request. The first one fetches; the rest wait and share its result, so nothing stampedes your origin. Your database feels one hit, not a thousand, and stays calm exactly when traffic doesn't.

Tablet
Laptop
Desktop
GraphPilot
Origin
1,000 identical requests 1 origin fetch
Dimension-aware caching

One query.

Many dimensions.

Personalized responses stay cacheable. Radiogramm, a small expression language built into GraphPilot, reads straight from each request (a JWT claim, a header, a cookie) and derives a cache key from it. Write one line, and the same query splits into exactly the buckets your data varies on, with no leak between them.

Prefer to decide at the origin? Return a Vary response header and the same split happens server-side, no rule required.

jwt.sub ?? "public" // one bucket per signed-in userRadiogramm
u_8f3kAda Lovelace{ cart: 3 }
u_2p9xGrace Hopper{ cart: 1 }
publicsigned out{ cart: 0 }

one query · separate buckets · no bucket sees another's data

Global edge

Served from the

nearest edge location.

Repeat reads are answered close to your users on a global network, so felt speed holds up worldwide, not just next to your origin.

Single POP Multiple POPs Planned new POP Planned upgrade
Map reflects the Fastly network GraphPilot deploys to, current as of June 8, 2026. Subject to change. Source: Fastly .
Fastly is a trademark of Fastly, Inc. GraphPilot is not affiliated with or endorsed by Fastly.
Build vs buy

You could build this.

Then you own it forever.

Storing a GraphQL response takes an afternoon. Keeping every cached read correct as data changes, and as your schema keeps evolving, is a distributed-systems problem you would maintain for the life of the product.

Build it yourself

The hidden work
  • Write purge rules for every mutation, entity, list and derived result.
  • Handle the edge cases: empty lists, missing ids, partial selections.
  • Re-verify correctness every time the schema changes.
  • Operate it: monitoring, staleness bugs, on-call.

weeks to build, then permanent maintenance

Drop in GraphPilot

Handled for you
  • Invalidation derived from your schema, automatically.
  • Empty lists, key-field injection and partial selections handled.
  • Policy updates ship in the same pull request as the schema.
  • A managed edge, with no purge system of your own to run.

drop it in front, keep shipping features

Get started

Stop choosing between

fast and fresh.

Put GraphPilot in front of your GraphQL API and get edge speed with invalidation you can actually trust.