What a subgraph actually is

Three files that together define a deterministic function from chain events to queryable rows. Getting the mental model right saves you from most subgraph bugs.

1 of 8 in the Developer path intermediate 12 min

Checked against Graph Horizon (2025-12-11)

Last read 2026-08-30 Due again 2026-11-30

Every protocol claim below was read at these sources on 2026-08-30. Where they disagree with each other, the lesson says so.

A subgraph is three files that together say: watch these contracts, and when this happens, write this.

  • subgraph.yaml, the manifest. Which contracts, from which block, which events, and which handler runs for each.
  • schema.graphql, the schema. Which entities exist and what fields they have. This becomes both your storage model and your query API.
  • mappings, usually AssemblyScript. The handlers themselves. An event comes in, an entity goes out.

Deploy those and an indexer replays chain history through your handlers, materialising your entities, then keeps following the chain. Applications query the result over GraphQL.

The property everything depends on.

A subgraph must be deterministic. Given the same chain data, every indexer running it must produce byte-identical results.

This is not a style preference. It is the requirement that makes the whole protocol work. Indexers publish Proofs of Indexing, hashes committing to what they derived, and the security model rests on honest indexers producing matching hashes. If your subgraph could produce different output on different machines, honest indexers would disagree, and disputes would fire against people who did nothing wrong.

So the mapping runtime forbids everything that could vary:

  • No network calls. You cannot fetch a price from an API mid-handler.
  • No wall-clock time or randomness. Only block time and block data.
  • No filesystem, no environment.
  • Only chain state and what you have already written.

Once you internalise this, a whole class of design questions answers itself. “Can I call an oracle here” is no. “Can I use the current time” is no, use block time. “Can I read from a database I control” is no.

Where the difficulty actually lives.

Not in the syntax. The syntax is a day’s work. These are where the time goes:

Modelling. The schema is the API your consumers live with, and changing it later means a new deployment and a resync. Entity identity is the load-bearing decision: what is the ID of this thing, and is it stable across every event that touches it?

Reorgs. The chain can retract blocks. The framework handles unwinding, and your mapping still has to be written so that unwinding makes sense, which mostly means not accumulating state in ways that cannot be reversed.

Factories and dynamic sources. Contracts deployed by contracts mean the set of addresses to watch grows during indexing. Template data sources handle this and they are a common source of confusion.

Contract upgrades. A proxy upgrade changes behaviour under a stable address. Your mapping was written against the old ABI. This is the most common way a working subgraph starts failing without anyone touching it.

Indexing time. Reading a large history through handlers is slow. If your backfill takes days, that is a design constraint, and it is a large part of why Substreams exists.

What it is not.

  • Not a server. You do not run it. You define it, and indexers execute it.
  • Not a database you write to. The only writer is your mapping, and its only input is chain data.
  • Not able to see anything off chain. If your product depends on off-chain state, that stays in your own systems.
  • Not free to run. It needs signal to be indexed at all, which is the curator path’s subject and a real cost to plan for.
Before reading on: why can a mapping not call an external API, even a highly reliable one?

Because reliability is not the issue. Reproducibility is.

The protocol’s guarantee is that anyone can re-derive the same data from the same chain and check it. An external call breaks that in two ways: the API can return different answers at different times, and it can disappear entirely, making history unverifiable at any point in the future.

Even a perfectly reliable API answering identically forever would still be a problem, because now the truth of your subgraph depends on a service nobody in the protocol can audit. You have imported a trusted third party into a system whose purpose is not having one.

If you need off-chain data in your product, join it outside the subgraph, in your own application, where trust assumptions are yours to make.

Check yourself

Why must subgraph mappings be deterministic?

Which is the most expensive decision to change later?

A working subgraph suddenly starts failing without anyone changing it. Most likely cause?