Skip to main content
Subgraphs are the fundamental building blocks of The Graph protocol. A subgraph defines how to extract, transform, and store blockchain data in a structured way that can be efficiently queried via GraphQL.

What is a Subgraph?

A subgraph is a declarative specification that describes:
  • What data to index from a blockchain (events, transactions, blocks)
  • How to transform that data (mapping functions in AssemblyScript/WASM)
  • What to store (entities defined in a GraphQL schema)
  • How to query that data (auto-generated GraphQL API)
Think of a subgraph as a custom API for blockchain data. Instead of scanning through thousands of blocks and transactions, you query a structured database that’s been automatically maintained by Graph Node.

Subgraph Manifest

Every subgraph starts with a manifest file (subgraph.yaml) that serves as its entry point. The manifest specifies all the information required to index and query a specific subgraph.

Manifest Structure

specVersion: 0.0.4
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: MyContract
    network: mainnet
    source:
      address: "0x1234..."
      abi: MyContract
      startBlock: 12345678
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - User
        - Transaction
      abis:
        - name: MyContract
          file: ./abis/MyContract.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts
Location in codebase: Parsed in core/src/subgraph_manifest.rs

Key Manifest Components

Indicates which version of the subgraph manifest specification is being used. Current version is 0.0.4.Starting from specVersion 0.0.4, subgraphs must explicitly declare features they use (like nonFatalErrors, fullTextSearch, grafting, etc.).Reference: docs/subgraph-manifest.md:1-194
Points to the GraphQL schema file that defines the entities (data models) your subgraph will store.
schema:
  file: ./schema.graphql
The schema file can be local or hosted on IPFS.
Defines the blockchain data sources to index. Each data source specifies:
  • kind: Type of data source (e.g., ethereum/contract, near/receipt)
  • name: Identifier for the data source
  • network: Blockchain network (mainnet, goerli, etc.)
  • source: Contract address, ABI, and start block
  • mapping: How to transform the data (handlers, entities, ABIs)
Multiple data sources can be defined in a single subgraph.
Data source templates enable dynamic data source creation. Templates have the same structure as data sources but without a contract address.
templates:
  - name: Exchange
    kind: ethereum/contract
    network: mainnet
    source:
      abi: Exchange
    mapping:
      # ... mapping configuration
Templates are instantiated at runtime when your mapping code calls DataSourceTemplate.create(address).Use case: Index factory-created contracts (e.g., all Uniswap pairs created by the factory).

Data Sources

Data sources define what blockchain data triggers your subgraph’s indexing logic.

Data Source Types

Static Data Sources

Defined in the manifest with fixed addresses and start blocks. Begin indexing when Graph Node deploys the subgraph.

Dynamic Data Sources

Created at runtime from templates. Useful for indexing contracts created by factories.

Handler Types

Data sources can define multiple types of handlers:
Triggered when a specific smart contract event is emitted.
eventHandlers:
  - event: Transfer(indexed address,indexed address,uint256)
    handler: handleTransfer
Implementation: chain/ethereum/src/adapter.rs extracts event logs and matches them to handlers.Performance note: Event handlers are the most common and efficient way to index blockchain data.
Triggered when a specific smart contract function is called.
callHandlers:
  - function: createPair(address,address)
    handler: handleCreatePair
Note: Requires archive node with trace data. More expensive than event handlers.
Triggered for every block (or filtered blocks).
blockHandlers:
  - handler: handleBlock
    filter:
      kind: call
Filter types:
  • call: Only process blocks containing calls to the data source contract
Use case: Track time-based state changes or aggregate data per block.

Entity Schema

The GraphQL schema defines the data models (entities) your subgraph stores. Graph Node automatically generates database tables and GraphQL queries based on this schema.

Schema Example

type User @entity {
  id: ID!
  address: Bytes!
  balance: BigInt!
  transactions: [Transaction!]! @derivedFrom(field: "user")
  createdAt: BigInt!
  updatedAt: BigInt!
}

type Transaction @entity {
  id: ID!
  hash: Bytes!
  from: User!
  to: User!
  value: BigInt!
  timestamp: BigInt!
  blockNumber: BigInt!
}

Supported Field Types

  • Scalars: ID, String, Int, BigInt, BigDecimal, Boolean, Bytes
  • References: Link to other entities (e.g., from: User!)
  • Arrays: Lists of scalars or references (e.g., [Transaction!]!)
  • Derived fields: Reverse lookups using @derivedFrom
Schema generation: store/postgres/src/relational.rs generates PostgreSQL tables from the schema.

Mapping Functions

Mapping functions are written in AssemblyScript (compiled to WebAssembly) and define how to transform blockchain data into entities.

Mapping Example

import { Transfer } from "../generated/MyContract/MyContract"
import { User, Transaction } from "../generated/schema"
import { BigInt } from "@graphprotocol/graph-ts"

export function handleTransfer(event: Transfer): void {
  // Load or create sender
  let sender = User.load(event.params.from.toHex())
  if (sender == null) {
    sender = new User(event.params.from.toHex())
    sender.address = event.params.from
    sender.balance = BigInt.fromI32(0)
    sender.createdAt = event.block.timestamp
  }
  
  // Update sender balance
  sender.balance = sender.balance.minus(event.params.value)
  sender.updatedAt = event.block.timestamp
  sender.save()
  
  // Load or create receiver
  let receiver = User.load(event.params.to.toHex())
  if (receiver == null) {
    receiver = new User(event.params.to.toHex())
    receiver.address = event.params.to
    receiver.balance = BigInt.fromI32(0)
    receiver.createdAt = event.block.timestamp
  }
  
  // Update receiver balance
  receiver.balance = receiver.balance.plus(event.params.value)
  receiver.updatedAt = event.block.timestamp
  receiver.save()
  
  // Create transaction record
  let transaction = new Transaction(event.transaction.hash.toHex())
  transaction.hash = event.transaction.hash
  transaction.from = sender.id
  transaction.to = receiver.id
  transaction.value = event.params.value
  transaction.timestamp = event.block.timestamp
  transaction.blockNumber = event.block.number
  transaction.save()
}

Runtime Execution

Mapping functions execute in a sandboxed WebAssembly environment:
  1. Trigger matching: DataSource::match_and_decode() determines if a trigger matches the data source
  2. WASM invocation: RuntimeHost invokes the handler function
  3. Host functions: Mapping code calls host functions to access data and store entities
  4. Entity operations: entity.save() persists changes to PostgreSQL
Location in codebase: runtime/wasm/src/host_exports.rs implements host functions available to mappings.

Available Host Functions

Entity Operations

entity.save(), Entity.load(), store.remove()

Ethereum Data

ethereum.call(), access to block, transaction, event data

IPFS

ipfs.cat(), ipfs.map()

Utilities

Crypto functions, JSON parsing, logging

Subgraph Lifecycle

1. Development

# Initialize subgraph
graph init --from-contract 0x1234...

# Generate types from schema and ABIs
graph codegen

# Build WASM module
graph build

2. Deployment

# Deploy to local Graph Node
graph create my-subgraph --node http://localhost:8020
graph deploy my-subgraph --ipfs http://localhost:5001 --node http://localhost:8020
Internal flow:
  1. Files are uploaded to IPFS
  2. Manifest is validated
  3. SubgraphRegistrar creates deployment record
  4. Database schema is generated
  5. SubgraphInstanceManager starts indexing
Location in codebase: core/src/subgraph/registrar.rs, core/src/subgraph/instance_manager.rs

3. Indexing

Once deployed, Graph Node automatically indexes the subgraph:
  1. Block stream creation: Start from startBlock defined in manifest
  2. Trigger extraction: Extract relevant events/calls/blocks
  3. Handler execution: Execute mapping functions for each trigger
  4. Entity persistence: Store entities to PostgreSQL
  5. Progress tracking: Update subgraph cursor
Location in codebase: core/src/subgraph/runner.rs

4. Querying

Queries are executed against the stored entities:
{
  users(first: 10, orderBy: balance, orderDirection: desc) {
    id
    address
    balance
    transactions(first: 5) {
      hash
      value
      timestamp
    }
  }
}
Query endpoint: http://localhost:8000/subgraphs/name/my-subgraph

Advanced Features

Grafting

Grafting allows a subgraph to be initialized from another subgraph’s state at a specific block, rather than starting from genesis.
graft:
  base: QmBase... # Base subgraph IPFS hash
  block: 12345678 # Block number to graft from
features:
  - grafting
Use case: Quickly deploy bug fixes without re-indexing from genesis. Reference: docs/subgraph-manifest.md:168-174

Declared Calls

Declared calls are eth_call operations that run in parallel before handler execution, improving sync performance.
eventHandlers:
  - event: Swap(indexed address,uint256,uint256)
    handler: handleSwap
    calls:
      getReserves:
        call: UniswapV2Pair[event.address].getReserves()
Mappings access results via standard ethereum.call(). Available from: specVersion 1.2.0 Reference: docs/subgraph-manifest.md:99-131 Enable full-text search on entity fields:
type Article @entity {
  id: ID!
  title: String! @fulltext(name: "articleSearch", algorithm: rank, language: en)
  content: String! @fulltext(name: "articleSearch")
}
Query example:
{
  articleSearch(text: "blockchain") {
    id
    title
  }
}
Feature flag: Must include fullTextSearch in manifest features.

Subgraph Components in Codebase

SubgraphRegistrar

Handles subgraph deployment and validationcore/src/subgraph/registrar.rs

SubgraphInstanceManager

Manages lifecycle of running subgraph instancescore/src/subgraph/instance_manager.rs

SubgraphRunner

Executes subgraph indexing logiccore/src/subgraph/runner.rs

RuntimeHost

WASM runtime for executing mappingsruntime/wasm/src/host.rs

Best Practices

Performance tip: Use event handlers whenever possible. They’re more efficient than call handlers and don’t require archive nodes.
  1. Start block optimization: Set startBlock to the deployment block of your contract to avoid indexing unnecessary blocks
  2. Entity design: Use derived fields instead of storing redundant data
  3. Handler efficiency: Minimize entity loads and saves in handlers
  4. ID strategy: Use deterministic IDs (e.g., transaction hash + log index) to ensure uniqueness
  5. Error handling: Handle null cases when loading entities

Next Steps

Indexing Process

Learn how Graph Node processes blocks and executes handlers

Query Execution

Understand how queries are translated to SQL and optimized

Architecture

Explore Graph Node’s overall architecture

Subgraph Manifest Spec

Full subgraph manifest specification

Build docs developers (and LLMs) love