Subgraph Development Guide: Indexing Dapp Data with The Graph

What you'll build: a subgraph for querying on-chain data
Subgraph development means using The Graph to define how blockchain data from smart contracts is indexed, transformed, and exposed through GraphQL, so your dapp can query historical events, relationships, and aggregates without scanning raw blocks on every request.
By the end of this guide, you will have a practical build path for a first subgraph: choose the data your product needs, design entities, write mappings, deploy, query, and monitor errors. Treat this as product infrastructure, not only a GraphQL exercise. The same discipline matters in Web3 mobile app development, where slow reads quickly damage the user experience.
What a subgraph contains
A subgraph has three core files. The manifest tells the indexer which contracts, networks, events, and start blocks to watch. The schema defines the GraphQL entities your app will query. The mappings turn raw event logs into saved entities.
As a freshness marker, the official Graph blog reported more than 1 trillion queries by January 2024. That number does not mean every dapp needs a subgraph. It does show that indexing has become a normal production pattern for historical Web3 data.
Why raw blockchain data is hard to use
RPC calls are useful for current state, but they are awkward for history. If your UI needs a user's transfers, pool snapshots, votes, or positions across months of activity, repeated log scans can become slow and expensive.
The practical rule is simple: use direct RPC for fresh, narrow reads; use a subgraph when your product needs reusable historical data with relationships. Gavin Wood, founder of Polkadot and co-founder of Ethereum, has long framed Web3 as protocol-driven infrastructure. A subgraph fits that idea only when the indexed data remains verifiable against the chain.
Prerequisites: install the tools and choose your network
Set up your environment before writing schema code. Most beginner errors come from a wrong ABI, a missing start block, or deploying to the wrong target.
What you'll need before you start
- Node.js: use an active LTS version; the Node.js project lists release status and end-of-life dates in its official release table, accessed March 2026.
- Graph CLI: install with
npm install -g @graphprotocol/graph-cli. - Contract address: copy the deployed address for the contract you want to index.
- ABI: save the contract ABI as a JSON file in your project.
- Start block: use the deployment block or the block just before it.
- RPC endpoint: choose a reliable endpoint for the chain you are indexing.
- Wallet: connect the wallet you use for deployment and account access.
- Deployment target: decide between subgraph studio, the decentralized network, a local node, or a managed host.
Basic TypeScript knowledge helps. Mappings are written in AssemblyScript, which looks similar to TypeScript but has stricter types and a smaller runtime.
Choose a deployment target
The hosted service migration matters because old tutorials still reference it. The official migration post set a hosted service transition deadline of June 12, 2024 for remaining subgraphs. In 2026, a beginner should usually build in subgraph studio first, then decide whether production belongs on the decentralized network or a managed provider.
There is no single fixed fee that applies to every project. Cost depends on query volume, chain support, indexing complexity, and provider pricing. Start with a development deployment, measure real query traffic, then choose a production path.
Warning: start block mistakes slow everything down
Warning: do not set
startBlockto0unless the contract was deployed at genesis. On Ethereum mainnet, that can force the indexer to scan years of irrelevant blocks before it reaches your first event.
Open your contract on a block explorer, find the deployment transaction, and copy that block number. This single field can save hours during an initial sync.
Step 1: define the on-chain data your dapp needs
Start with your product, not the ABI. List the screens your users will see, then write the exact questions each screen must answer.
Map product questions to contract events
Product question | Event to index | Entity to create |
|---|---|---|
What positions does this wallet have? | Borrow(user, amount) | Position |
Which NFTs moved between wallets? | Transfer(from, to, tokenId) | Transfer |
How many votes did a proposal receive? | VoteCast(voter, proposalId, support, weight) | Vote |
How did pool liquidity change over time? | Sync(reserve0, reserve1) | PoolSnapshot |
Use this table as a small data inventory. If a field does not answer a product question, leave it out until you have a real query for it.
Choose event handlers, call handlers, and templates
Event handlers should be your default. They are predictable because the contract emits the data explicitly.
Call handlers are useful when a function changes state without emitting the event you need. Check chain support before depending on them.
Active data sources, often called templates, are needed when a factory contract creates new child contracts. If your protocol creates pools, vaults, or markets at runtime, your subgraph must discover and index those child contracts.
Pro tip: index only what your UI queries
Pro Tip: before adding an entity, ask which frontend query will read it. If you cannot name the query, do not index the field yet. Small schemas sync faster, are easier to test, and are less painful to migrate.
Step 2: design the schema and relationships
Your schema.graphql file defines the API your frontend will use. Design it around stable entities, not around every raw event parameter.
Create entities for users, transactions, pools, and positions
Entity | Recommended ID | Key fields |
|---|---|---|
User | lowercase wallet address | positionCount, createdAt |
Transaction | transaction hash plus log index | blockNumber, timestamp, amount |
Pool | pool address | token0, token1, feeTier |
Position | token ID or pool plus owner | liquidity, owner, updatedAt |
Keep each field on the entity that owns it. Pool-level values belong on Pool, not on every transaction row, unless the transaction needs a point-in-time snapshot.
Use derivedFrom for reverse lookups
The @derivedFrom directive creates a reverse relationship without storing a growing array on the parent entity. For example, if Transaction has user: User!, then User can expose transactions: [Transaction!]! @derivedFrom(field: "user").
This keeps storage smaller and makes queries clearer. Your frontend can still fetch a user's transaction history from the user entity.
Warning: bad IDs create duplicate data
Warning: entity IDs must be deterministic and safe during chain reorgs. If the same event is processed again, it must produce the same ID.
Use composite IDs when one field is not unique enough. A transaction hash alone is not safe when one transaction emits multiple matching logs. Use event.transaction.hash.toHexString() + '-' + event.logIndex.toString().
Step 3: write mappings that transform events into entities
Mappings are the code that turns event data into saved GraphQL entities. Keep them boring and predictable: load, create if missing, set fields, save.

Handle smart contract events
Each handler receives an event object. Your function should read event parameters, load the target entity, set every required field, and call entity.save().
A common beginner bug is forgetting save(). The code may build successfully, but the data will never appear in queries.
Convert blockchain types safely
On-chain value | Mapping conversion | Common mistake |
|---|---|---|
Address | .toHexString() | Using inconsistent casing for IDs |
uint256 amount | Convert to decimal after applying token decimals | Dividing too early or losing precision |
Timestamp | event.block.timestamp.toI32() | Using a type your schema does not expect |
IPFS metadata | ipfs.cat(cid) then parse JSON | Skipping null checks on missing content |
For the binary details behind ABI values, read the Solidity ABI encoding and decoding guide before finalizing handlers. It explains why a value that looks simple in a block explorer may need careful conversion in code.
Pro tip: test mapping logic before deployment
Use matchstick-as tests for handlers before deploying. Create mocked events for the happy path, missing entity path, and edge cases such as zero amounts.
Then add graph codegen, graph build, and mapping tests to your smart contract CI/CD pipeline. Catching a null error locally is much cheaper than waiting for a full re-sync.
Step 4: generate, build, and deploy the subgraph
Now turn your files into a live endpoint. Run each command locally before deployment so build errors appear while they are easy to fix.
Initialize the project with the command-line tool
Run graph init. The prompt asks for protocol, network, contract address, ABI path, and start block. Enter the deployment block you found earlier, not block zero.
The tool scaffolds subgraph.yaml, the schema file, and a starter mapping file. The public tooling release history is maintained in the graphprotocol tooling releases, accessed March 2026, which is the safest place to check current version behavior before following an older tutorial.
Run codegen and build before deploying
Run graph codegen. This creates typed AssemblyScript classes for your entities and contract bindings.
Next, run graph build. The build checks schema validity, manifest integrity, and mapping compilation. If it fails, read the file and line number in the terminal output before changing multiple things at once.
Deploy to a development endpoint
Authenticate with your deploy key, then run graph deploy with your subgraph slug. In the web dashboard, open the deployment page and confirm that indexing has started before sharing the query URL with your frontend team.
Do not publish to a production target until you have real query examples and a sync status you trust. Balaji Srinivasan, author and former Coinbase CTO, often emphasizes verifiable digital systems; apply that habit here by checking your indexed values against chain data before treating the API as reliable.
Pro Tip: after deploying, open the indexing status page and check current block, latest chain block, and handler errors. A deployment that is still syncing may return incomplete data.
Step 5: query, monitor, and fix indexing errors
Your subgraph is deployed, but you are not done. Test the exact queries your frontend will run, then monitor sync lag and handler errors.
Test GraphQL queries like a frontend developer
- Recent transactions:
{ transfers(first: 10, orderBy: timestamp, orderDirection: desc) { id from to amount } } - User positions: filter with
where: { user: "0x..." }. - Token balances: compare indexed values with a block explorer for the same block.
- Protocol aggregates: query totals such as volume, user count, or TVL only after confirming the event math.
The GraphQL API docs describe pagination with first and skip, and list 1,000 entities as the maximum value for first, documented in 2024 and accessed March 2026. For larger lists, prefer cursor-style pagination with id_gt instead of large skip values.
Debug failed syncs and handler errors
Error | Likely cause | Fix |
|---|---|---|
Missing entity | The handler loads an entity before it exists | Add a null check and create a default entity |
ABI mismatch | The event signature does not match the deployed contract | Correct the ABI, then rerun codegen |
Null required field | The schema marks a field as non-null, but the mapping never sets it | Set the field before save() |
Wrong start block | The start block is after the first relevant event | Redeploy from the correct deployment block |
Reorg data gap | IDs or assumptions are not deterministic | Use stable IDs based on transaction hash and log index |
Keep mappings pure. Do not make live RPC calls inside handlers, because the same block must always produce the same entity writes.
When an error appears, copy the block number from the logs, open that block in a block explorer, and inspect the raw event. Most first fixes are small: a null guard, a corrected type, or an event signature update.
Tradeoffs: when you should and shouldn't use a subgraph
Subgraph development is powerful, but it is not always the right answer. Use the 5-axis infrastructure fit test below: latency, history, cost, maintenance, and developer experience.
Compare subgraphs, RPC, custom indexing, SQL, and managed APIs
Option | Best for | Limitations | Maintenance burden | Typical use case |
|---|---|---|---|---|
Subgraphs | Historical queries, joins, aggregates, reusable public data | Sync lag, schema migration work, indexing constraints | Medium | Dashboards, marketplaces, governance explorers |
Direct RPC | Fresh single-block reads and simple wallet state | Poor for long history, aggregation, and heavy pagination | Low | Balance checks, latest contract state, simple reads |
Custom indexer | Special data shapes, private pipelines, sub-second targets | High engineering and operations cost | High | Trading systems, proprietary analytics, compliance exports |
SQL database | Relational queries after your own ETL process | Centralized trust assumption and pipeline ownership | Medium to high | Apps combining chain data with user records |
Managed API | Fast prototypes and small teams | Vendor lock-in, less control, pricing risk at scale | Low | MVPs, hackathon apps, early analytics tools |
This is the contrarian point: subgraphs are excellent for reusable historical data, but they are not a universal default. If you need under-50-millisecond private reads from a specialized model, a custom pipeline may fit better.
Choose a provider with a checklist, not a logo
- Uptime evidence: look for published status pages and incident history.
- Network support: confirm your exact chain and testnet are supported.
- Pricing model: compare per-query pricing with flat plans using your own expected traffic.
- Migration path: ask whether you can export data or move deployments later.
- Support process: production apps need ticketed support, not only chat replies.
- Runtime compatibility: check the supported manifest spec and mapping features.
Warning: decentralized does not mean maintenance-free
Warning: decentralization can reduce infrastructure dependency, but your team still owns schema design, handler logic, versioning, and monitoring.
Every contract upgrade or new event can require a subgraph update. Treat your schema the way you treat a database schema: review it, test it, version it, and document it.
Summary and next steps for subgraph development
You now have a build path for subgraph development: define product data, design entities, write mappings, deploy, query, and monitor. Before production, run the checklist below.

Your practical checklist
- Data needs confirmed: each entity maps to a product query and a contract event or call.
- Schema reviewed: relationships use
@derivedFromwhere useful. - IDs are deterministic: transaction hash plus log index is used when one transaction can emit several events.
- Mappings tested: local tests cover normal, empty, and edge-case events.
- Deployment checked: indexing status shows progress without handler errors.
- Queries documented: frontend developers receive example queries and pagination rules.
- Versioning planned: breaking schema changes get a new deployment path.
Where to go after your first deployment
Next, learn active data sources so your subgraph can index contracts created by a factory. That pattern matters for pools, vaults, markets, and other protocols that deploy child contracts over time.
Then plan for multi-chain data. If your protocol expands across several networks, agree on shared entity names and ID conventions early. For the broader cross-chain picture, read the guide to DeFi interoperability across chains.
- Automate deployment: run codegen, build, tests, and deploy steps from CI.
- Plan migrations: decide how you will rename fields or remove entities without breaking active clients.
- Tune performance: paginate large lists, reduce nested query depth, and profile slow handlers.
Subgraph development is product infrastructure. Build it with the same care you give a REST API, database schema, or smart contract release, and your dapp's data layer will be much easier to scale.

Frequently Asked Questions
- What is a subgraph in The Graph?
- A subgraph is an indexing layer that turns blockchain data from smart contracts into a searchable GraphQL API. It lets your dapp query historical events, relationships, and aggregates without scanning raw blocks every time.
Sources
Author

Crypto analyst and blockchain educator with over 8 years of experience in the digital asset space. Former fintech consultant at a major Wall Street firm turned full-time crypto journalist. Specializes in DeFi, tokenomics, and blockchain technology. His writing breaks down complex cryptocurrency concepts into actionable insights for both beginners and seasoned investors.


