- Grafting lets you skip millions of blocks by copying an existing subgraph’s data up to a specific block number — cutting re-index time from days to minutes.
- Multi-contract dataSources let a single subgraph track minting, transfers, and metadata from different contracts at the same time.
- Setting an accurate startBlock is one of the easiest performance wins you can make — it tells Graph Node exactly where your contract was born.
- Use eventHandlers first, callHandlers only when you need function-level detail, and blockHandlers only for periodic snapshots — because order matters for speed.
- Substreams open up cross-chain indexing for non-EVM chains like Solana, Arweave, and Stellar with high-performance parallel pipelines.
- Chia Network developers will recognize the core logic: just like a Chialisp puzzle validates a spend before it executes, a subgraph handler validates and transforms on-chain data before it gets stored.
This the graph indexing tutorial advanced guide picks up where beginner subgraph setup leaves off. It covers grafting, multi-contract manifests, handler selection strategy, cross-chain Substreams, schema design for analytics, and how to run your own Graph Node — everything you need to move from a working prototype to a production-grade indexing system.
What Makes a Subgraph “Advanced”?
Most introductory tutorials stop after you deploy your first subgraph, watch it sync, and run a basic GraphQL query. That’s enough to prove the concept works. But production applications have harder requirements: faster sync times, data from multiple contracts, time-series analytics, and sometimes data flowing from chains that don’t even use the EVM. Advanced subgraph development is the art of meeting those requirements without rebuilding your entire indexing layer from scratch.
The Three Problems Advanced Developers Actually Face
The first problem is re-indexing pain. If you fix a bug or add a new field to your schema, a basic subgraph has to replay every block from the contract’s deployment — which can take hours or days on a busy chain. The second problem is data fragmentation: your protocol might span three contracts, but beginners often build three separate subgraphs and then struggle to query relationships across them. The third problem is handler performance. Not all three handler types — event, call, and block — index at the same speed, and picking the wrong one for the job creates unnecessary slowdowns that compound over millions of blocks.
Understanding these three core problems shapes everything else in this guide. Each technique below is a direct solution to one of them.
Subgraph Grafting — The Graph Indexing Tutorial Advanced Method for Fast Iteration
Grafting is the most powerful developer productivity tool in the advanced subgraph toolkit. When you graft a new subgraph onto an existing one, Graph Node copies all the entity data from the base subgraph up to and including a block you choose, then continues indexing new blocks from that point forward. The result: instead of waiting hours for a full re-sync, you can test schema changes or mapping bug fixes in minutes.[1]
Grafting is a development and emergency tool — it copies rather than re-indexes historical data, so it’s much faster but not recommended for your first production deployment to The Graph Network.
How Grafting Works in Your Manifest
Grafting requires just two additions to your subgraph.yaml. You add grafting to the top-level features array, then define a graft block that points to the base subgraph’s deployment ID and the block number where you want to take over. The manifest snippet looks like this:
specVersion: 0.0.6
features:
- grafting
graft:
base: QmBaseSubgraphDeploymentHash
block: 18200000
dataSources:
- kind: ethereum/contract
name: MyToken
...
When you deploy this manifest, Graph Node checks that the base subgraph exists and has already indexed up to your chosen block. It then copies all entity data up to that block into your new subgraph and starts indexing fresh blocks from there. The grafted subgraph’s GraphQL schema doesn’t have to be identical to the base — it just has to be compatible, meaning you can add new fields or entity types.[2]
When NOT to Use Grafting
Grafting has a few firm limits. It only works if the Indexer has already indexed the base subgraph — that’s a hard dependency. And while grafting is safe for iteration in Subgraph Studio, The Graph docs explicitly recommend deploying your initial production subgraph without grafting, then using it for subsequent updates once the base is stable on the decentralized network. Think of it as a diff rather than a full deployment: clean first launch, graft-on-top for updates.[1]
Advanced Handler and Feature Quick Reference
| Situation | Best Tool | Why | Watch Out For |
|---|---|---|---|
| You changed a mapping and need to re-test fast | Grafting | Copies base data instead of re-indexing from genesis | Base subgraph must be indexed by your target node |
| Your protocol spans 3 contracts | Multi-contract dataSources | One subgraph, one API, relational queries across all contracts | Entity ID collisions across contracts |
| Contract emits events for every state change | eventHandlers | Fastest indexing path — native to Graph Node | None — always prefer events when available |
| Contract has no events, only function calls | callHandlers | Captures full function inputs/outputs | Slower than event indexing; not available on all networks |
| You need periodic snapshots (hourly TVL, etc.) | blockHandlers with call filter | Runs per-block; filter limits it to blocks with contract calls | Runs every block without a filter — very expensive |
| You need to index Solana, Arweave, or Stellar | Substreams | Parallel Rust pipelines for non-EVM chains | Requires learning Substreams module API |
Multi-Contract Indexing — One Subgraph for Your Whole Protocol
Most DeFi and NFT protocols aren’t a single contract. A token launch might have a minting contract, a metadata registry, and a royalty splitter — all deployed at different blocks. Beginner devs often build one subgraph per contract, then write application-layer code to join the data. Advanced developers do it the right way: declare all contracts as separate dataSources entries in a single subgraph.yaml. This gives you one unified GraphQL endpoint where entities from all three contracts can be queried together with relationships intact.[3]
Adding Multiple dataSources in Your Manifest
The structure is straightforward. Each contract gets its own block under dataSources, with its own name, ABI, address, startBlock, and handler list. You can reference different mapping files or share one mapping.ts file across all sources — whatever your logic requires. One important detail: every startBlock should be set to the actual deployment block of that specific contract. Setting it to 0 or leaving it out forces Graph Node to scan from the chain’s genesis, which can add millions of unnecessary block checks and dramatically slow initial sync.
dataSources:
- kind: ethereum/contract
name: NFTMint
network: mainnet
source:
address: "0xMintContractAddress"
abi: NFTMint
startBlock: 17500000
mapping:
kind: ethereum/events
...
- kind: ethereum/contract
name: MetadataRegistry
network: mainnet
source:
address: "0xMetadataContractAddress"
abi: MetadataRegistry
startBlock: 17502000
mapping:
kind: ethereum/events
...
Dynamic Data Source Templates — Tracking Contracts You Don’t Know Yet
Some protocols — like Uniswap V2 and V3 — deploy new pair or pool contracts dynamically. You can’t hardcode these addresses into your manifest because they don’t exist at deployment time. Data source templates solve this. You define a template in your manifest under templates, and then inside your AssemblyScript mapping, you call TemplateName.create(newContractAddress) whenever the factory emits a “new pool” event. Graph Node instantly starts indexing that newly created contract using your template rules.[3]
Dynamic data source templates are the reason protocols like Uniswap can be fully indexed by a single subgraph despite spawning thousands of pool contracts — this is one of the most powerful patterns in advanced subgraph development.
Handler Types in The Graph Indexing Tutorial Advanced Context
Handler selection is the single biggest performance lever available to you in a subgraph. Graph Node supports three: eventHandlers, callHandlers, and blockHandlers. They are not interchangeable, and using the wrong one can make a subgraph 10x slower than it needs to be. Understanding when each one is appropriate is a core skill in advanced subgraph work.
eventHandlers — The Speed Champion
Event handlers should always be your first choice. When a Solidity contract emits an event, the event data gets included in the transaction receipt — making it fast and cheap for Graph Node to retrieve. You map a specific event signature to an AssemblyScript handler function, and your handler fires with a strongly-typed object containing all the indexed and non-indexed event parameters. Because event data is already extracted and structured in the receipt, indexing with events is far faster than parsing full call data. If your smart contracts don’t emit events for the state changes you care about, the most effective thing you can do is update the contracts to add them before deploying your subgraph.[4]
callHandlers — When Events Aren’t Enough
Call handlers fire when a specific function on your contract is called, giving you access to both the input parameters and the return values of that function. This is valuable for contracts written without adequate events, or when you need to capture data that only exists in the call itself and isn’t emitted anywhere. The tradeoff is speed: call handlers are indexed slower than event handlers because Graph Node has to decode the full transaction call data rather than reading a receipt. Use them when you genuinely have no event to listen to, and not as a convenience shortcut.[4]
blockHandlers — Time-Based Snapshots
Block handlers run on every block unless you add a filter. Without a filter, a block handler tied to an active contract will fire thousands of times per hour, making it the most expensive handler type by far. The practical use case for block handlers is creating time-series data — hourly or daily snapshots of a protocol’s TVL, for example. When you use a call filter, the handler only fires on blocks that contain at least one call to your contract, which dramatically limits the execution frequency. Always use this filter unless you have a specific reason to process every block.[4]
Schema Design for Advanced Subgraph Analytics
The schema is where advanced and beginner subgraphs diverge the most. A beginner schema mirrors the contract events 1:1 — one entity per event type, one field per parameter. That works fine for simple lookups but falls apart when you need aggregates, historical data, or per-user statistics. Advanced schema design derives higher-level entities from raw event data during the indexing phase, so your API returns pre-computed answers instead of making the client do expensive aggregation.
Derived Fields and Aggregate Entities
The GraphQL schema supports a @derivedFrom directive that creates virtual reverse-lookup fields between entities. For example, if a Pool entity has many Swap entities, you can add swaps: [Swap!]! @derivedFrom(field: "pool") to the Pool type and query all swaps for a pool without storing a redundant array on the Pool entity. This keeps your store lean while giving consumers powerful relational queries. For aggregates, the best pattern is to create a separate entity — say, PoolDayData — and update its cumulative fields (volume, fees, liquidity) inside your event handlers every time a relevant event fires. The data is ready when the query arrives, not computed on demand.
Time-Bucketed Entities for Protocol Analytics
Many DeFi dashboards need data points at daily, hourly, or weekly intervals. The standard pattern is to create entities like ProtocolDayData keyed by a compound ID of the protocol address plus the Unix timestamp rounded down to the nearest day. Inside your event handler, you compute the day’s timestamp bucket, load or create the day-data entity for that bucket, update its fields, and save it. This gives you an O(1) lookup for any time range query — something that would require a full scan if you only stored raw events.
“The Graph is an indexing protocol for organizing data from blockchains and storage networks and making it easily accessible.”— Yaniv Tal, Co-Founder, The Graph Protocol [5]
Cross-Chain Indexing with The Graph — Substreams and Multi-Network Strategy
The Graph’s original subgraph system is built around EVM-compatible blockchains — Ethereum, Arbitrum, Polygon, Avalanche, Base, and dozens more. But modern Web3 applications increasingly span non-EVM chains, and that’s where Substreams come in. As of 2024, The Graph supports fast, no-code Solana indexing through Substreams, and the protocol has also extended to Arweave, Stellar, and NEAR.[6]
Substreams — High-Performance Parallel Pipelines
Substreams are a different abstraction from subgraphs. While a subgraph uses AssemblyScript handlers that run sequentially, Substreams are modular data pipelines written in Rust that execute in parallel. This makes them dramatically faster for high-throughput chains or historical data catchup scenarios. A Substreams module takes a stream of blocks as input, applies your transformation logic, and outputs structured data that can feed directly into a subgraph (via Substreams-powered subgraphs) or into any other downstream consumer. The Graph also supports pre-built Substreams modules from companies like Messari and Top Ledger, so you can deploy standard DeFi or NFT indexers without writing any Rust at all.[7]
Cross-Network Subgraph Design Patterns
If your protocol is deployed on both Ethereum and Arbitrum, you have two options: deploy separate subgraphs for each network and merge data at the application layer, or use The Graph’s multi-network support to point separate dataSources entries to different chains. The cleaner pattern for most teams is separate subgraphs per network with shared schema and mapping code, since entity IDs and block contexts differ by chain. The Graph now distributes indexing rewards exclusively on Arbitrum One, so all new subgraph deployments go there by default as of 2024.[8]
The Chia Connection — Chialisp Puzzles and Subgraph Handlers Share a Core Idea
If you’ve worked with Chialisp on Chia Network, the mental model for advanced subgraph handlers will feel familiar. In Chialisp, every coin is locked by a puzzle — a pure function that takes a solution as input and either approves or rejects the spend. In a subgraph, every event handler is a pure function that takes event data as input and transforms it into structured entities. Neither function stores state directly; both return a result (a spend bundle or an entity update) that the underlying system — Chia’s mempool or Graph Node — commits to the ledger. The key difference is that Chialisp executes on-chain to validate transactions, while AssemblyScript handlers execute off-chain to organize the data those transactions produce. Understanding this parallel helps Chia developers approach subgraph development as a natural extension of the execution model they already know — just applied to data retrieval rather than asset security. You can read more about Chialisp’s smart contract model in our guide to how Chialisp is transforming blockchain smart contracts.
Subgraphs vs Substreams — Which One Do You Need?
| Feature | Subgraphs (AssemblyScript) | Substreams (Rust) |
|---|---|---|
| Language | AssemblyScript | Rust |
| Execution model | Sequential, per-event or per-block | Parallel, modular pipeline |
| Best for | EVM chains, relational data, GraphQL APIs | Non-EVM chains, high-throughput, real-time streams |
| Output | GraphQL API with entity store | Structured data stream (can feed a subgraph or external sink) |
| Learning curve | Moderate — TypeScript-like syntax | Higher — requires Rust knowledge |
| Cross-chain support | EVM-compatible chains | EVM + Solana, Arweave, Stellar, NEAR |
| Pre-built modules | Limited | Growing library (Messari, Top Ledger, etc.) |
| Indexing speed | Fast for EVM | Significantly faster — parallel execution |
Running Your Own Graph Node
For most developers, Subgraph Studio and the decentralized network handle infrastructure completely. But there are cases where running your own Graph Node instance makes sense: private chains or enterprise testnets, extremely high query volumes where cost is a concern, or research environments where you want full control over the indexing stack. Graph Node is open source, written in Rust, and designed to run alongside a Postgres database and an IPFS node.[9]
When Self-Hosting Makes Sense
The decision to self-host is a tradeoff between control and operational overhead. A Graph Node instance needs consistent Ethereum (or target chain) RPC access, a properly tuned Postgres instance, and enough disk space to store the entity store — which can grow large for heavily-used subgraphs. The Firehose integration, used for Substreams, is a separate high-speed gRPC streaming layer that significantly reduces sync times for large chains and is the recommended RPC replacement for production self-hosted setups. If you’re already running blockchain infrastructure for Chia or other chains, the operational patterns are familiar: you’re essentially running another full-node adjacent service that processes and stores chain state in a queryable format.
Case Study: Uniswap’s Subgraph Architecture
Uniswap is one of the most referenced examples of production-grade subgraph design. Uniswap V3’s subgraph uses dynamic data source templates to track every pool deployed by the factory contract — and by 2025, that means tracking thousands of active pools across multiple chains simultaneously through separate network-specific subgraphs. The protocol uses time-bucketed entities (PoolHourData, PoolDayData, TokenDayData) to power its analytics dashboard, all pre-computed at indexing time so queries return in milliseconds. Uniswap’s subgraph design is widely used as a reference architecture in the developer community precisely because it demonstrates every advanced technique — templates, aggregates, and multi-contract sources — working together at scale.
Conclusion
Mastering the graph indexing tutorial advanced techniques means more than just knowing the syntax. Grafting gives you fast iteration cycles. Multi-contract dataSources give your protocol a single, unified API. Choosing the right handler type — event first, call only when needed, block with a filter — keeps your subgraph lean and fast. Substreams open the door to non-EVM chains and high-throughput scenarios. And designing for analytics from the start, with derived fields and time-bucketed entities, means your GraphQL API delivers pre-computed answers rather than pushing aggregation work onto the client. Whether you’re building on Ethereum, Solana, or drawing parallels from your Chialisp work on Chia, the principles are the same: define your data model clearly, handle events at the lowest level available, and let the indexing infrastructure do the heavy lifting. Start with one technique from this guide, apply it to a real contract you’re working with, and build from there.
the graph indexing tutorial advanced FAQs
What does “advanced” mean in a the graph indexing tutorial advanced context?
In a the graph indexing tutorial advanced context, “advanced” refers to going beyond basic event handler setup to techniques like grafting, multi-contract dataSources, dynamic templates, and Substreams for non-EVM chains. These techniques address production requirements like fast re-indexing, cross-contract queries, and real-time data pipelines that a beginner setup doesn’t support.
What is grafting in The Graph and when should I use it?
Grafting in The Graph lets you initialize a new subgraph by copying all entity data from an existing “base” subgraph up to a chosen block, then indexing only new blocks from that point. Use it during development to fix mapping bugs or add schema fields quickly, and for stable production updates — but avoid it on your very first production deployment to the decentralized network.
How do I index data from multiple smart contracts in a single subgraph?
To index multiple smart contracts in one subgraph, add a separate entry under the dataSources array in your subgraph.yaml for each contract, each with its own address, ABI, startBlock, and handlers. For contracts created dynamically at runtime (like Uniswap pool contracts), use data source templates and call TemplateName.create(address) from inside your mapping when a factory event fires.
Is the graph indexing tutorial advanced approach relevant for non-EVM chains like Solana?
Yes — the graph indexing tutorial advanced techniques apply to non-EVM chains through Substreams, not traditional subgraphs. Substreams are Rust-based parallel data pipelines that The Graph introduced for Solana, Arweave, Stellar, and NEAR, and as of 2024, The Graph supports fast no-code Solana indexing through pre-built Substreams modules.
Why is choosing the right handler type so important for subgraph performance?
Handler type choice is critical because eventHandlers, callHandlers, and blockHandlers index at very different speeds. Event handlers are the fastest because they read from transaction receipts. Call handlers are slower because they decode full call data. Block handlers without a filter run on every single block and can make a subgraph extremely slow — always use the call filter to limit block handler execution to blocks containing contract interactions.
the graph indexing tutorial advanced Citations
- The Graph Official Docs — Replace a Contract and Keep its History With Grafting
- The Graph Official Docs — Advanced Subgraph Features
- The Graph Node — Subgraph Manifest Specification (GitHub)
- The Graph Academy — Defining a Subgraph: callHandlers, blockHandlers, eventHandlers
- DeFi Prime — Interview with Yaniv Tal, Co-Founder of The Graph
- Wikipedia — The Graph (protocol)
- Messari — State of The Graph Q4 2025
- Messari — State of The Graph Q2 2025
- GitHub — graphprotocol/graph-node (Graph Node Source)
