What Is GraphPilot? The Beginner's Guide to GraphQL Edge Caching With Automatic Invalidation

CodexFuse Team · 2026-11-05T14:54:25.486Z · 40 min read min read

What Is GraphPilot? The Beginner's Guide to GraphQL Edge Caching With Automatic Invalidation

What Is GraphPilot?

GraphPilot is a managed, Rust-written proxy that caches GraphQL responses at the edge and purges them automatically when a mutation changes the data underneath. It sits in front of your API, reads your schema to decide what to cache, how long to hold it, and what to clear when your data changes, and it serves repeat reads from the Fastly edge network instead of your origin. The vendor, a German company, published the product on November 4, 2026, one day before this article was written, so everything below comes from its launch pages and documentation, not from independent testing.

The short version is that the GraphPilot edge cache does for GraphQL what a CDN does for static files, with one extra trick. A normal CDN cannot tell two GraphQL requests apart, because they share one URL and carry their differences inside the request body. GraphPilot parses the query, so it knows exactly which objects a response contains, and when a mutation returns one of those objects, it purges every cached response that mentioned it, usually within seconds at every edge location.

That combination is the whole pitch: cache hits answer in 9 to 18 milliseconds at the edge in the vendor's demo benchmark, and the cache stays correct without hand-written purge rules. If you want a quick sense of how the underlying hosting model fits into a budget, start with our guide to VPS hosting, which explains the managed-server model GraphPilot extends to the edge, and keep a SHA-256 hash generator handy, because GraphPilot identifies each cached response by a SHA-256 hash of the query text.

FactGraphPilot at a glance
What it isManaged GraphQL edge cache: a Rust proxy on the Fastly edge network
LaunchedNovember 4, 2026 (vendor site and docs)
How you use itPoint clients at a GraphPilot service hostname; no new gateway, no client code changes
Caching modelSchema directives set lifetimes; surrogate keys tag every cached response by entity
InvalidationAutomatic and mutation-driven; purges land within seconds at every edge location
Standout featureKey-field injection, so a response stays invalidatable even when the client forgets the id
Free tierGlider: 100,000 request units a month, then lean mode until the window resets
Paid startJet: 127 euros a month base, plus 6 euros per started 1 million request units over 500,000
Self-hostNone: hosted and managed only, with no infrastructure for you to run
Open sourceNot claimed: the vendor publishes the gpilot CLI through GitHub releases, and nothing fetched says the proxy itself is open source

GraphPilot also caches plain HTTP APIs, not only GraphQL, and it works in front of a federated graph as one cache, not one cache per subgraph. It verifies credentials, limits what a request may carry, and signs every request it forwards to your origin, so the origin can trust that traffic came through GraphPilot. You describe a service with one config file plus your schema, and there is no infrastructure to run, which is the point of a managed edge: the customer runs nothing, and the product deploys onto the Fastly edge network.

One naming note, said once: always use the full vendor context, "GraphPilot, the GraphQL edge cache (graphpilot.io)". The name is shared with unrelated projects, and the disambiguation is in the limits section at the end.

Why Is GraphQL So Hard to Cache?

GraphQL edge caching is hard for three reasons, and the vendor's homepage states all three plainly. First, every query and mutation shares one URL, with the operation and variables buried in the request body, so a CDN sees one blind endpoint and cannot tell a product list from a user profile. Second, a clock cannot tell what changed: short TTLs throw away hit rate, and long TTLs serve stale data.

Third, invalidation is a distributed system: a single write invalidates entities, lists, and derived results across many cached responses, and hand-written purge rules are fragile.

The root cause is that GraphQL treats HTTP as a dumb pipe. Most servers accept every operation as a POST to one endpoint, and HTTP caches will not cache POST requests, so the standard web-caching machinery never engages. As the Apollo team puts it, "GraphQL breaks caching" only lacks nuance: the protocol itself is cacheable, but the way servers use HTTP defeats the caches built into the web.

GraphQL.org makes the same point from the other side: in an endpoint-based API, the URL is a globally unique identifier for a resource, while in GraphQL there is no URL-like primitive that identifies a given object, so the API must expose a global identifier, such as an id field, before object-level caching can work at all.

Teams usually work around this in one of four ways. Some switch queries to GET, which caches do store by default, but query strings have size limits, so large queries break. Some use persisted queries, sending a short hash instead of the full query text, which GraphQL.org recommends for exactly this reason.

Some cache on the client, with a normalized store such as Apollo Client that keeps objects by global id, but that only helps one browser, not the origin. And some put a plain CDN in front and write custom edge code, which is what Fastly's own GraphQL demo does, because, in Fastly's words, "it is very difficult to achieve good caching when using GraphQL because all requests are POST requests". GraphPilot is a packaged version of that last approach, with the schema doing the work the custom edge code used to do.

To see the underlying delivery model, read what a CDN is: a network of edge servers that answers repeat reads close to the user instead of going back to the origin every time.

The problemWhy it bitesGraphPilot's answer
One URL for everythingEvery query and mutation is a POST to the same endpoint, so a CDN cannot tell them apartParses the query at the edge and keys the cache by the query text, operation name, and variables
No object identity in the URLNothing in the request identifies which objects a response containsSurrogate keys tag each cached response with the entities inside it, such as Product:id:42
A clock cannot tell what changedShort TTLs waste hits; long TTLs serve stale dataMutations purge the affected keys within seconds, so TTLs can stay long without going stale
One write, many cached responsesA single edit can hide in lists, derived results, and several queries at onceOne purge by surrogate key clears every response that carries it, at every edge location
Per-user dataPersonalized responses cannot share one cache entryRadiogramm partitions the cache per user from a verified token claim, header, or cookie
Clients forget the idA query that omits the key field leaves a response that cannot be purged by entityInjects the key fields at the edge, so every cached response stays invalidatable

The table above is the mental model for the rest of this article. GraphQL edge caching is not one trick but a chain: parse the query, tag the response with the entities it contains, and purge by those tags when a mutation returns them. Break any link in that chain and the cache either misses everything or serves stale data, which is why the vendor spends most of its documentation on the tagging and purging steps rather than on the caching step.

How Does GraphPilot Work?

The GraphPilot GraphQL edge cache is a proxy that sits in front of your API, and clients only switch their endpoint to use it. A request arrives at the nearest edge location, GraphPilot reads the query, and if the response is already cached it answers from the edge; only misses and writes reach your origin. Mutations are never cached: they always go to the origin, and their responses are what trigger the purges.

Subscriptions are relayed as a WebSocket tunnel and are not cached or inspected, and streamed responses are not supported, so there is no server-sent events, multipart/mixed, @defer, or @stream through the proxy.

Every cached response gets two labels. The cache key identifies exactly one cached response, and it is built as the route plus a SHA-256 hash of the query text, with the operation name and a hash of the variables appended when present. The surrogate keys are labels that can sit on many responses at once, such as Product:id:42 for one product or Product for the whole type, and purging a surrogate key removes every response that carries it.

GraphPilot reads a mutation's response to know which entities to clear, so the purge follows from the keys both the query and the mutation were tagged with, and you write no purge call, annotate nothing, and configure nothing for the common case.

GraphPilot also protects the origin. It verifies credentials, limits what a request may carry, and signs every request it forwards, so the origin can check the signature and trust only traffic that came through the proxy. The docs' go-live checklist says to verify that signature at the origin and to keep the origin clock in sync, because the signature is time-bound.

Request collapsing is a second origin-protection feature: when a spike sends the same query all at once, GraphPilot collapses the identical requests into a single origin fetch, so 1,000 identical requests cause 1 origin fetch and 999 of them never reach your servers.

What arrivesWhat GraphPilot doesWhat the client sees
Repeated query, cachedAnswers from the nearest edge locationgp-cache: HIT, with gp-cache-hits, gp-cache-age, and gp-cache-max-age headers
First query, or expired entryFetches from the origin, stores the response, tags it with surrogate keysgp-cache: MISS on the first response, then HIT on the repeat
Query that cannot be storedPasses it through without storing, for example when the origin sets a cookie or a Cache-Control: private headergp-cache: PASS, with a reason code such as CACHE_SKIPPED_SET_COOKIE
MutationForwards to the origin, reads the returned objects, and purges every cached response tagged with themThe next identical query is a MISS with fresh data, then a HIT again
SubscriptionRelays it as a WebSocket tunnelLive updates, with nothing cached or inspected
Streamed responseNot supported: no server-sent events, multipart/mixed, @defer, or @streamUse a direct connection for streaming, not the proxy

Two details in that table deserve emphasis. First, GraphPilot does not decide on its own that a response may be reused: either the origin sends a Cache-Control header with a lifetime, or the schema states one with a directive, and if neither does, nothing is stored. Second, the cache decision is strict about what it refuses: responses with a Set-Cookie header, auth headers, GraphQL errors, Vary: *, unparseable JSON, or error statuses are never stored, and neither are responses that select fields the deployed schema does not declare.

A response whose surrogate keys do not fit the 16,000-byte per-response budget is not cached at all, rather than cached with keys missing, which is the safe failure mode for a cache whose whole point is correct invalidation.

What Do @surrogateKey and @cacheControl Do in a GraphPilot Schema?

GraphPilot reads two kinds of rules from your schema, and they do different jobs. The @surrogateKey directive says how a cached response is tagged, so it can be found and purged later. The @cacheControl directive says how long a response may be reused, following the same rules as Apollo Server's cache-control plugin.

Together they are the whole configuration model for caching: lifetimes from @cacheControl, identity from @surrogateKey, and everything else from the origin's headers or the config file.

The @surrogateKey directive is repeatable, so one type can carry several keys. A field-level key names one field, such as id: ID! @surrogateKey, which produces Product:id:42 for the product with id 42. A type-level key names several fields at once, such as @surrogateKey(of: "region currency"), which produces one key per combination, with the fields sorted alphabetically.

Composite keys multiply out over list values, so @surrogateKey(of: "brand tags") on a product tagged new and bestseller yields both Product:brand:acme:tags:new and Product:brand:acme:tags:bestseller. Keys declared on an interface are inherited by every type that implements it, and Apollo Federation's @key can be read instead, through configuration.

Some keys need no directive at all. A field whose type is a key scalar, ID by default, or whose name is a key name, id, _id, or key by default, is treated as a key field automatically, and you can change those defaults in gpilot.toml or switch one off per field. Keys are printable ASCII only, so Müller becomes Muller, the German sharp s becomes ss, and spaces become underscores, and no single key may exceed 1,024 bytes.

Only entity keys and origin keys are purged automatically by a mutation; type keys, operation keys, and static keys are never purged automatically, though they can be purged on demand through the API, the CLI, or the Cockpit portal.

type Product
  @cacheControl(maxAge: 3600, scope: PUBLIC, swr: 3600)
  @surrogateKey(of: "id")
  @surrogateKey(of: "category.id")
  @surrogateKey(static: "catalogue") {
  id: ID!
  name: String!
  price: Int!
  inventory: Inventory! @cacheControl(maxAge: 5, scope: PUBLIC)
}

The example above is the vendor's own demo catalog schema, and it shows how the two directives combine. The product type may be cached for one hour, the inventory field for five seconds, and the whole response takes the minimum lifetime across every field it selects, so a query that includes inventory is cached for five seconds, not one hour. The swr argument is the stale-while-revalidate window: five seconds after the entry was stored it is served stale while a background refresh runs, and stating swr: 0 disables serving stale entirely, which is what you want when a stock level must never be served stale.

The @cacheControl directive takes four arguments: maxAge in seconds, scope of PUBLIC or PRIVATE, inheritMaxAge, and swr. A field hint overrides the type hint only for the arguments it sets, and the rest come from the type, exactly as in Apollo Server. An explicit maxAge on a field always wins; otherwise a field with inheritMaxAge: true takes its parent's resolved value; otherwise a root field or a field returning a composite type falls back to the default maxAge of 0, which means not cacheable; otherwise a scalar leaf inherits its parent's value.

An origin Cache-Control header that states a lifetime always wins over the schema, and a resolved maxAge of 0 means the response is not cached at all.

One discrepancy is worth stating plainly, because the vendor's own pages disagree. The homepage's marketing example writes type Product @cacheRule(maxAge: 300), but every docs page documents @cacheControl, and the docs say the directive name is configurable. Treat @cacheRule as the homepage's shorthand: the documented directive for lifetimes is @cacheControl, and the code samples in this article use the documented name.

The same rename rule applies to @surrogateKey, whose name is also configurable, though the docs use that name throughout.

How Does Automatic Invalidation Work?

GraphQL cache invalidation is the feature the product is named around, and it works without any configuration in the common case. Every mutation that goes through GraphPilot invalidates the cached responses that contained the objects it returned. You write no purge call, annotate nothing, and configure nothing: it follows from the surrogate keys both the query and the mutation were tagged with.

A mutation response is tagged exactly like a query response, and GraphPilot purges every entity key on it, every origin key from the mutation response's Surrogate-Key header, and the empty-list keys for every type the mutation touched.

The worked example in the docs makes the precision concrete. A setPrice mutation returning Product:id:p01 purges exactly Product:id:p01, plus the empty-list keys for Mutation, Product, and Review, and it does not purge the operation key m:setPrice or the type key Review. That restraint is deliberate: clearing every cached response that so much as mentions reviews would empty most of your cache on a single write.

Purges are hard, meaning the entry is removed and the next request is a miss, with no soft-purge or serve-stale option on the automatic path, and they usually land within seconds at every edge location. Every automatic purge is recorded in the service's purge history in the Cockpit, with the cleartext keys and the triggering mutation, when telemetry is stored.

There are also gaps, and the docs state them explicitly, which is to the vendor's credit. A create does not appear in a cached non-empty list: if addReview creates a fourth review, the purge clears only the new review's entity key and the empty-list key, neither of which matches the cached three-review list, so the list keeps three entries until its TTL expires. A mutation that returns nothing purges nothing: deleteReview(id: ID!): Boolean! returns no object, carries no entity key, and therefore invalidates nothing at all, so the docs tell you to return the affected object or its parent instead.

Static keys are never purged automatically, and there is no automatic purge by type or by operation. If your mutation returns a Relay-style payload type, GraphPilot looks one level below the return type for keyed types, with a configurable depth that defaults to 1 and can be switched off with 0.

For cases the automatic path cannot cover, the manual path is simple. You can purge by key or purge everything, from the API, the CLI with gpilot purge, or the Cockpit portal, and purges are free: they are not counted as request units. If a mutation returns only success: true, your origin can list the affected entities as cache tags in the response header, and GraphPilot purges exactly those, with no schema change and no extra API call.

And purges keep running even when the billing account is over quota and the cache has stopped serving, which is a deliberate choice: a stale cache that cannot be purged would be worse than a cache that is off.

How Does GraphPilot Keep One User's Data From Another's?

Per-user data is the second hard part of GraphQL caching, because a response built for one signed-in user must never be served to another. GraphPilot handles it with partitioning: the same query can have one cache entry per user, split by a value read from the request. The mechanism is Radiogramm, a small expression language built into the product, which reads straight from each request, a JWT claim, a header, or a cookie, and derives a cache key from it.

The vendor's example rule is jwt.sub ?? "public", which gives one bucket per signed-in user and one shared bucket for anonymous traffic.

The docs advise basing splits on verified token claims rather than on headers or cookies, because a header is easy to forge and a token claim is not. The origin can also return a Vary response header for server-side splits, which is the standard HTTP way to say that a response depends on part of the request. The schema side of the same idea is the scope argument on @cacheControl: a field marked scope: PRIVATE is never shared between users, while PUBLIC entries are shared, and a missing scope counts as PUBLIC only when neither hint states one.

For a field returning a union or interface, one PRIVATE member makes the whole field private, whichever member the query selects, and there is no opt-out, because the origin decides what the field returns.

This is where the minimum-lifetime rule and the scope rules meet, and it is worth seeing them together. A response's lifetime is the minimum maxAge across every field it selects, so the most restrictive field sets the lifetime for the whole response. Scopes accumulate instead of taking the minimum: every member's scope joins the field's scope, so a private member anywhere in the selection makes the entry private.

The deployment build reports the effective scope in the portal's Cache rules view, and the logs carry a CACHE_POLICY_SCHEMA line with the resolved maxAge, the fields that set it, whether the result is private, and whether the scope defaulted to PUBLIC, which is the first thing to check when one caller reports seeing another caller's data.

How Fast Is GraphPilot?

The vendor's headline figure is a p95 cache-hit response time of 9 to 18 milliseconds, measured in a demo benchmark run on October 2, 2026, and shown on the homepage. It is a vendor-measured demo benchmark, not a guaranteed service level, and the docs say so in plain language: the figures "illustrate the effect of edge caching; they are not a guaranteed service level", and "GraphPilot does not publish a fixed number of edge locations and does not guarantee a specific latency". Treat the numbers as a controlled demonstration of what edge caching does, not as a promise about your API.

The method is documented in enough detail to judge. The metric is k6's http_req_duration, the p95 HTTP response time of cache hits measured at the load generator, which sums the sending time, the time to first byte, and the body download, including the network round trip to the nearest edge location. It excludes DNS lookup and TCP/TLS setup, because k6 keeps connections open and each virtual user pays the setup cost once.

The load came from three cloud regions, 40 percent Europe, 30 percent US East, and 30 percent Asia, with the percentiles formed across all three combined, and each request was served by the edge location nearest its region. The setup was a dedicated GraphPilot service on the production edge, in front of a demo origin in a single European region, with a fixed set of deterministic, cacheable queries, so the response bytes were identical on every run.

Each size class ran the same protocol: a warm-up of 5 virtual users for 15 seconds, so the first request per edge fills the cache, then a measurement of 20 virtual users for 60 seconds in a closed loop with no think time. The whole run took 75 seconds, inside the benchmark queries' 300-second maxAge, so no entry could expire mid-run, leaving 225 seconds of headroom. Every measurement-phase request was checked for a 200 status and a gp-cache: HIT header, so a miss would fail the check instead of passing as a hit.

The docs also ran cache-miss tests and a stress ramp from 20 to 2,000 requests per second, but no miss numbers are published, and no head-to-head comparison against any competitor exists.

Response classResponse sizeMeasured p95 cache hitShown on homepage
Small (one item)101 bytes8.2 ms9 ms
Medium (300 items)About 22 KB10.0 ms10 ms
Typical (700 items)About 50 KB12.0 ms12 ms
Large (1,500 items)About 381 KB17.8 ms18 ms

The arithmetic in that table is the interesting part. The large response is about 3,862 times the size of the small one, 390,029 bytes against 101, yet the p95 rises by only 9.6 milliseconds, from 8.2 to 17.8. The docs explain why: on a cache hit, the response time grows with response size because the body download grows, and "that growth is bandwidth between the edge and the load generator, not cache lookup time".

In other words, the cache lookup itself is a small, flat cost, and the rest is moving bytes over the network, which is also why the docs warn that a client on home Wi-Fi or a mobile network adds its own last mile on top of these figures, while a client in a well-connected data center sees something close to them.

One more caution belongs here. The benchmark ran 34 days before this article, on October 2, and the product launched on November 4, so the figures describe a demo service under controlled conditions, not a production fleet with months of history. The docs also note that load tests over GraphPilot need the vendor's prior consent under its terms.

If you run your own measurements, use the curl recipe on the benchmark page: a MISS or PASS includes origin time, while a HIT does not, so only HIT timings are comparable to the published figures.

How Do You Set Up GraphPilot?

Setup is five steps, and the docs' quickstart walks through all of them against a demo catalog API. You sign up in the Cockpit portal, create an organization and confirm business use, then create a service with your origin URL and a subdomain. The first service can take up to an hour, because a certificate has to be issued for its hostname, which follows the pattern of a subdomain plus a label under graphpilot.cloud.

You install the gpilot CLI, add your schema and a config file, deploy, and verify with the gp-cache header. The first deployment is a plain pass-through with no schema and default configuration, and GraphPilot then introspects the origin and offers to apply a suggested configuration and schema.

  1. Sign up in the Cockpit at cockpit.graphpilot.io, create an organization, and create a service with your origin URL and a subdomain.
  2. Install the gpilot CLI with brew install graphpilot/tap/gpilot, then run gpilot auth login, and check the version with gpilot --version.
  3. In a deploy directory, add schema.graphql with your @cacheControl and @surrogateKey directives, plus a gpilot.toml config file, which may be empty because every setting has a default.
  4. Run gpilot deploy, review the diff it shows, and let it upload and wait for go-live, knowing a failed build never goes live.
  5. Point your clients at the new service hostname, send the same query twice, and check the gp-cache header: the first response is a MISS and the second is a HIT.

The two files deserve a closer look, because they are the whole configuration model. The schema must be exactly the schema your API exposes, and gpilot schema pull can read it from a running API, but introspection carries no caching directives, so you add those by hand. The config file, gpilot.toml, covers request paths, cache policy, surrogate-key behavior, directive names, telemetry, shielding, client profiles, persisted queries, request signing, custom domains, and deployments, and every setting has a default, so an empty file is valid.

The default GraphQL path is /graphql, and other paths go under [request] graphql_paths in the config.

Verification is built into the headers. A second identical query should return gp-cache: HIT with headers such as gp-cache-hits: 1, gp-cache-age: 4, gp-cache-max-age: 60, and gp-cache-remaining: 56, which tell you how many times the entry has been served, how old it is, and how much lifetime it has left. If you see PASS instead, the usual causes are that the origin sets a cookie, that Apollo Server sent no-store, or that the hosting platform added Cache-Control: private.

Then send a mutation, such as updating a product's name, and repeat the query: the first response after the write is a MISS with the new data, and the next is a HIT again, and nothing was configured to make that happen, because the mutation's response carried the product's surrogate key.

Two practical notes from the docs belong in any setup plan. First, the CLI is a single self-contained binary for macOS, Linux, and Windows, also available through a shell installer, Scoop, and manual downloads, and since version 1.0.1 the releases are Sigstore-signed, with the SHA-256 checksums published alongside. Second, GraphPilot is a managed edge, so deployments, staging, rollback, and CI deploy are portal and CLI operations, not servers you run: the product's own line is that there is no infrastructure to run, and the customer deploys configuration, not machines.

If your deployment story involves containers and clusters, read how to use Kubernetes for the orchestration side, and keep GraphPilot as the caching layer in front of it.

How Much Does GraphPilot Cost?

GraphPilot charges by request units, not by requests, and the difference matters for budgeting. One request unit is one started 10 KiB, or 10,240 bytes, of uncompressed response body, at every edge location a request passes through, with a minimum of one unit per request and headers excluded, and compression does not change the count. Purges are free and are not counted as request units.

A second meter, Insights, counts each logged request once against its own allowance, and the calculator example prices Insights overage at 12 euros per unit, though the per-plan Insights allowances were not captured in the fetched pricing text.

There are four plans. Glider is free, with 100,000 request units a month and no overage: when the allowance is used up, services switch to lean mode, with edge caching and stored telemetry off, while requests are still authenticated, signed, and forwarded, until the window resets or the plan changes. Jet, the starter plan, includes 500,000 units and charges 6 euros per started 1 million units above that, on a base price of 127 euros a month.

Airliner, the business plan, includes 5 million units and charges 5 euros per started 1 million above that. Jumbo, the enterprise plan, is priced per contract. All prices are net and exclude VAT, and the product is for business use only, not for consumers, which the Cockpit signup asks you to confirm.

The vendor's own calculator example makes the unit math concrete. One million client requests a month, at an average body of about 50 KB, comes to 5,000,000 request units, because each 50 KB response is about five units. On Jet, the first 500,000 units are included, so 4,500,000 are over the allowance, which is five started millions at 6 euros each, or 30 euros, and 127 plus 30 is 157 euros a month for that traffic.

The calculator says that is 55 euros a month less than Airliner for the same traffic, which puts Airliner at 212 euros a month in that example, though Airliner's base price itself was not stated in the fetched pricing text. For the base alone, Jet at 127 euros a month is 1,524 euros a year, and the free tier at 100,000 units a month works out to about 3,333 units a day.

PlanIncluded request units a monthBase priceAbove the allowance
Glider (free)100,0000 eurosNo overage: lean mode until the window resets
Jet (starter)500,000127 euros a month6 euros per started 1 million units
Airliner (business)5,000,000Not stated in the fetched pricing text5 euros per started 1 million units
Jumbo (enterprise)Per contractPer contractPer contract

The unit math also explains why response size matters as much as request count. A 101-byte response costs one unit, a 22,230-byte response costs three units, because 22,230 divided by 10,240 is 2.17, rounded up to the next started 10 KiB, and a 390,029-byte response costs 39 units, because 390,029 divided by 10,240 is 38.09. A small, chatty API can therefore cost more than a large, quiet one, and the way to lower the bill is to shrink responses or raise hit rates, not only to cut requests.

The plan limits around the meter are worth noting too: Glider funds one organization and no custom domain, Jet funds two organizations and two custom domains, Airliner funds three organizations and five custom domains, and Jumbo funds five custom domains or whatever the contract sets, with team seats unlimited on every plan and audit-log retention of 1, 30, 180, and 365 days across the four plans.

One warning from the pricing terms is worth reading before you build on the free tier. Glider is for trying out, development, testing, and small projects, it comes with no support commitment, and the terms let the vendor change or end the free plan at any time, with no notice period. Fair use applies, and traffic far beyond what the tier is meant for can lead to suspension and an account lock until the next month or an upgrade.

Paid plans are billed from activation, with the price charged at once in advance, monthly or yearly, and overage billed monthly in arrears, and plan changes go up at once and down at the end of the period.

GraphPilot vs Stellate vs APISIX: What Is Different?

GraphPilot is not the first product to cache GraphQL at the edge, and the comparison that matters is against the two established approaches: Stellate, the managed GraphQL CDN formerly called GraphCDN, and the Apache APISIX graphql-proxy-cache plugin, which is a self-hosted gateway plugin. All three answer the same question, how to stop every read from hitting the origin, but they split on who runs the cache, how invalidation works, and what a free tier buys. This section compares GraphPilot vs Stellate and GraphPilot vs GraphCDN, which are the same comparison, because GraphCDN is Stellate's former name, and then sets both against the do-it-yourself APISIX route.

Stellate is the closest rival. It calls itself the first GraphQL CDN, it was founded in March 2021 by Max Stoiber and Tim Suchanek, and it is built on Fastly Compute@Edge, the same edge network GraphPilot deploys to. Its feature set overlaps heavily: edge caching of GraphQL query results, rate limiting, metrics and insights, error tracking, partial query caching, a custom cache directive called @stellate_cache with maxAge, swr, and scope, and automatic plus manual purging, with the vendor claiming a purge lands in less than 150 milliseconds globally.

Its free tier is also 100,000 requests a month, the same headline number as GraphPilot's Glider, though the meters differ: Stellate counts requests, while GraphPilot counts request units by response size. Its Business plan starts at 249 dollars a month, with 25 million metrics requests included, and edge caching billed at an additional 5 dollars per million requests, against GraphPilot's Jet at 127 euros a month with 500,000 request units included. The currencies differ, so compare the structures, not the numbers: Stellate sells a broad platform with analytics and rate limiting bundled, while GraphPilot sells a narrower proxy focused on schema-driven caching and verifiable invalidation.

APISIX is the opposite end of the spectrum. Its graphql-proxy-cache plugin caches GraphQL query responses on disk or in memory, for GET and POST, with a cache key built as an MD5 hash of the plugin version, host, route, service, identity, and query body. Mutations are never cached and always bypass to the upstream, and there is no automatic entity invalidation at all: invalidation is a manual PURGE call by cache key only, with no schema awareness and no surrogate keys.

It is open source under Apache 2.0 and free to run, but you run APISIX yourself, you configure the cache zones in config.yaml first, and you own the operations. Against that baseline, GraphPilot's value is the automation: the schema declares the rules, and mutations purge by entity without anyone writing a purge call.

GraphPilotStellateAPISIX graphql-proxy-cache
TypeManaged GraphQL edge cache, Rust proxy on the Fastly edgeManaged GraphQL CDN and edge platform, on Fastly Compute@EdgeSelf-hosted API-gateway plugin
InvalidationAutomatic, mutation-driven, by entity surrogate keys; purge API, CLI, and CockpitAutomatic plus manual purge API, under 150 ms globally per the vendor; cache directivesManual PURGE by cache key only; mutations just bypass the cache
Schema directives@surrogateKey for identity; @cacheControl for lifetimes, Apollo-style@stellate_cache with maxAge, swr, and scopeNone; gateway configuration instead
Free tierGlider: 100,000 request units a monthFree: 100,000 requests a monthFree as open source; you pay for the infrastructure
Paid startJet base 127 euros a month, plus 6 euros per started 1 million units over 500,000Business from 249 dollars a month, 25 million metrics requests included; caching plus 5 dollars per millionNone: no vendor price
Published p959 to 18 ms cache hit, vendor demo benchmark, October 2, 2026About 40 ms response times worldwide, vendor claimNone published
Also doesPlain HTTP caching, federated graphs, request collapsing, per-user partitioningRate limiting, metrics and insights, error tracking, partial query cachingFull API-gateway duties beyond caching

The honest summary is that GraphPilot is the newest and narrowest of the three, and its bet is that schema-driven invalidation you can verify is worth more than a broader platform. Stellate has five years of history, a wider feature set, and case studies, including a Gamma and Hasura case study the vendor cites at an 87 percent cache hit rate, and APISIX has no vendor bill at all, but demands that you operate the gateway. GraphPilot's edge is the invalidation model: every purge is recorded with its keys and its triggering mutation, so you can see exactly what a write cleared, which neither rival exposes in the same way.

What it lacks is time in the market: the product launched on November 4, 2026, and no independent review, tutorial, or explainer of it existed when this article was written, so every claim in the comparison above is a vendor claim, not a verified result.

Who Should Use GraphPilot, and Who Should Not?

GraphPilot fits teams whose GraphQL API is read-heavy, whose reads repeat, and whose writes must show up quickly. A product catalog, a content API, a public directory, or a storefront fits the model: many clients ask for the same objects, the objects change through mutations that return them, and a cache hit in 9 to 18 milliseconds is worth more than a round trip to the origin. It also fits teams that already run a federated graph, because GraphPilot caches the federated graph as one, in front of any gateway, without a new gateway or client code changes.

And it fits teams that need per-user caching without building it themselves, because Radiogramm partitions the cache from a token claim, and the PRIVATE scope keeps one user's data out of another user's entry.

It fits less well when the workload is write-heavy, deeply personalized, or streaming. An API whose responses are mostly unique per user gets little from a shared cache, and the request-unit meter charges by response size, so large personalized responses cost units without producing hits. An API that streams, uses server-sent events, or relies on @defer and @stream cannot go through the proxy at all.

And a team that needs a self-hosted deployment, an on-premises install, or a private network has no option here: GraphPilot is hosted and managed only, with no self-host edition in any fetched page, and the product is for business use, not consumers.

The cost question decides most of the rest. The free tier is genuinely free, with no credit card required, and 100,000 request units a month covers a small API or a development environment, but it is not meant for production traffic, it carries no support commitment, and it can be changed or ended at any time under the terms. A production API on Jet starts at 127 euros a month before overage, which is the number to compare against the cost of the origin capacity the cache saves.

The request-unit meter rewards small responses and high hit rates, so an API that returns 50 KB lists to a million clients should do the calculator math first: that traffic is 5,000,000 units, or 157 euros a month on Jet, against whatever the origin would have spent answering a million requests itself.

Your situationVerdictWhy
Read-heavy GraphQL API with repeat queriesGood fitCache hits answer at the edge in 9 to 18 ms in the vendor demo, and mutations keep them fresh
Federated graph behind a gatewayGood fitCached as one graph, in front of any gateway, with no new gateway or client changes
Small API or development environmentGood fitGlider is free with 100,000 units a month and no credit card, though not for production
Per-user data with verified tokensGood fit, with careRadiogramm and PRIVATE scope partition the cache, but base splits on token claims, not headers
Write-heavy or mostly unique responsesWeak fitFew repeat reads means few hits, while the unit meter still charges by response size
Streaming, server-sent events, @deferNo fitStreamed responses are not supported through the proxy
Self-hosted or on-premises requirementNo fitHosted and managed only; no self-host edition exists in any fetched page

The last row of that table is the one to check first, because it is a hard constraint rather than a trade-off. Everything else is a question of hit rate and response size, which the free tier lets you measure: deploy the pass-through service, watch the gp-cache headers, and see what share of your traffic is actually repeatable before paying. The docs' own quickstart is built for exactly that test, and the purge history in the Cockpit shows whether your mutations are clearing what you expect.

What Are the Limits and Gotchas?

The limits fall into four groups: invalidation gaps, what is never stored, the newness of the product, and the name. The invalidation gaps are documented by the vendor itself. A create does not appear in a cached non-empty list until the list's TTL expires.

A mutation that returns nothing purges nothing, so return the affected object or its parent. Static keys are never purged automatically, and there is no automatic purge by type or by operation. If your API creates objects inside lists, or deletes with a bare Boolean return, plan for those cases in the schema, because the automatic path will not save you.

What is never stored is a long list, and most of it is good hygiene. Mutations and subscriptions are never cached. Responses with a Set-Cookie header, auth headers, GraphQL errors, Vary: *, unparseable JSON bodies, or error statuses are never stored, unless error statuses are explicitly opted in.

Responses that select fields the deployed schema does not declare are not stored, which usually means the origin was deployed ahead of its schema, so deploy the schema to GraphPilot first. Responses over 133 MiB are not parsed or stored, and a response whose keys exceed the 16,000-byte budget is not cached at all. None of that is a defect; it is a cache that refuses to store what it cannot invalidate correctly.

The newness is the largest caveat. GraphPilot launched on November 4, 2026, its benchmark ran on October 2 against a demo origin, and no independent review, tutorial, or explainer of the product existed when this article was written. The 9 to 18 millisecond figures are a vendor demo benchmark, not a service level, and there is no head-to-head latency comparison against any competitor.

The vendor's GitHub presence is real, the gpilot CLI is distributed through GitHub releases under the GraphPilot organization, but no star count, license, or language was verified for the vendor's repository, and the proxy itself should not be called open source. Aggregator sites list a Show HN post discovered on November 5, but no Hacker News item was directly verified, so this article does not cite a Show HN launch.

Finally, the name traps, said once each. The graphpilot-oss project is a different product: a local, open-source MCP server that indexes a TypeScript or JavaScript repo with tree-sitter so coding agents can query its structure, published as a dev.to post on June 10, 2026, with 17 stars at last check, and it has nothing to do with GraphQL caching and never makes a network call. "GraphPilot AI" is a different project again: a student team's Agentic GraphRAG engine, a Top 25 winner out of 463 submissions in the Pak Angels hackathon in early October 2026, and it is not a caching product either.

And the Fastly edge map on the vendor's homepage is current as of June 8, 2026, with the vendor stating plainly that GraphPilot is not affiliated with or endorsed by Fastly. If a search result about "GraphPilot" is not the GraphQL edge cache at graphpilot.io, it is one of the others.

Two smaller operational notes close the list. Your origin's clock must stay in sync, because the request signature GraphPilot adds is time-bound, and the first service can take up to an hour while its certificate is issued. Telemetry is stored in the EU for at most 90 days under a data processing agreement, which suits a German company selling to businesses, and the product's support commitment starts at the paid plans, with none on the free tier.

For the hosting side of the same picture, read cloud hosting, which explains the managed-infrastructure model GraphPilot pushes one step further, to the edge.

Sources

GraphPilot homepage, graphpilot.io, published November 4, 2026. GraphPilot documentation, docs.graphpilot.io, updated November 4, 2026, including the quickstart, automatic invalidation, surrogate keys, lifetime from the schema, performance and benchmarks, quotas, and CLI installation pages. GraphPilot pricing page, graphpilot.io/pricing, fetched November 5, 2026.

Stellate pricing page, stellate.co/pricing, fetched November 5, 2026. Apache APISIX graphql-proxy-cache plugin documentation, apisix.apache.org. Fastly GraphQL edge demo documentation, fastly.com.

GraphQL.org caching and performance pages, graphql.org. Apollo, "GraphQL and Caching: The Elephant in the Room", apollographql.com. The New Stack, "Stellate Expands on GraphQL with Caching and Other Tooling", thenewstack.io.

Dev.to, "I cut my coding agent's token usage 61% by giving it a code graph", published June 10, 2026. LinkedIn posts on the "GraphPilot AI" hackathon project, published October 2026. All benchmark, pricing, and capability figures in this article are vendor-reported, from the vendor's own pages, and none was independently verified.

Frequently Asked Questions

Is GraphPilot the same as the graphpilot-oss MCP server?

No. GraphPilot, the GraphQL edge cache at graphpilot.io, is a managed Rust proxy that caches GraphQL responses on the Fastly edge and purges them when mutations change the data. The graphpilot-oss project is a local, open-source MCP server that indexes a TypeScript or JavaScript codebase so coding agents can query its structure, and it never makes a network call.

The two share a name and nothing else, and the arXiv paper of the same name about autonomous driving is a third, unrelated use of it.

Can GraphPilot cache my GraphQL API if my origin sends no Cache-Control header?

Yes, through the schema. The @cacheControl directive states a lifetime per type or field, following Apollo Server's rules, and the response takes the minimum lifetime across every field it selects. An origin header that states a lifetime always wins over the schema.

If neither the origin nor the schema states a lifetime, nothing is stored, and GraphPilot reports the reason as CACHE_SKIPPED_NO_CACHE_POLICY.

Does a mutation always clear the right cache entries?

Usually, but not always. A mutation that returns the objects it changed purges every cached response tagged with those objects' surrogate keys, usually within seconds at every edge location. Two gaps are documented: a create does not appear in a cached non-empty list until that list's TTL expires, and a mutation that returns nothing, such as a delete returning a bare Boolean, purges nothing at all.

Return the affected object or its parent from the mutation, and the key comes with it.

How much does GraphPilot cost for a small API?

The Glider tier is free, with 100,000 request units a month and no credit card required, which is about 3,333 units a day, though it is not meant for production traffic and carries no support commitment. The first paid plan, Jet, starts at 127 euros a month with 500,000 units included, plus 6 euros per started 1 million units above that. One request unit is one started 10 KiB of uncompressed response body, so a small API with small responses can serve many requests inside the free allowance, while a million 50 KB responses is 5,000,000 units, or 157 euros a month on Jet.

Is 9 to 18 milliseconds a guaranteed response time?

No. It is the p95 cache-hit response time from the vendor's demo benchmark, run on October 2, 2026, with k6 load generators in three cloud regions against a demo origin, and the vendor states plainly that it is a reproducible demo benchmark, not a guaranteed service level. Actual latency depends on geography, cache hit rate, query shape, response size, and your own network, and a client on home Wi-Fi or a mobile network adds its own last mile on top.

No head-to-head latency comparison against any competitor has been published.

Featured on Twelve Tools Hunt for Tools Featured on LaunchBuff Launch Llama Tools Launch your MCP Top Web Development Companies in the US - OnToplist.com Designer Listings Listed on SeeWhatNewAI Featured on SubmitMySaas