T
iTokenly

Subgraph Development Guide: Indexing Dapp Data with The Graph

Marcus Reynolds··Web3 & Development·Guide
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 startBlock to 0 unless 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.

Monochrome The Graph subgraph mapping diagram transforming events into entities with save warning

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.

Checklist infographic summarizing subgraph development readiness for indexing with The Graph

Your practical checklist

  • Data needs confirmed: each entity maps to a product query and a contract event or call.
  • Schema reviewed: relationships use @derivedFrom where 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.

  1. Automate deployment: run codegen, build, tests, and deploy steps from CI.
  2. Plan migrations: decide how you will rename fields or remove entities without breaking active clients.
  3. 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.

The Graph subgraph development diagram linking smart contract events to GraphQL API Dapp queries

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.

Author

Marcus Reynolds - Crypto analyst and blockchain educator
Marcus Reynolds

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.

Related articles