Skip to content
Learni
View all tutorials
Blockchain

How to Deploy a The Graph Subgraph in 2026

Lire en français

Introduction

The Graph is the essential decentralized indexing protocol for Web3 dApps in 2026. It lets you query blockchain data via GraphQL, outperforming slow RPCs for complex queries like Uniswap swaps or ERC20 transfers. Why use it? Imagine querying 1M+ events in milliseconds instead of hours of scanning.

This advanced tutorial guides you step-by-step to index Transfer events from an ERC20 token on Sepolia (address: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238). We cover CLI installation, GraphQL schema, AssemblyScript mappings, local build with Docker, playground queries, and deployment to Subgraph Studio. Result: a production-ready queryable GraphQL endpoint. Ideal for DeFi dashboards or NFT analytics. Estimated time: 2 hours for an experienced dev.

Prerequisites

  • Node.js 20+ and Yarn 1.22+
  • Docker and Docker Compose (for local graph-node)
  • Alchemy or Infura API key (Sepolia RPC)
  • Subgraph Studio account (free at thegraph.com/studio)
  • Advanced knowledge of Solidity, GraphQL, and TypeScript
  • Git installed

Install the The Graph CLI

terminal
npm install -g @graphprotocol/graph-cli
yarn global add @graphprotocol/graph-cli
graph --version

Install the official CLI via npm or Yarn to handle init, codegen, and deploy. Verify with graph --version (should show 0.38+ in 2026). Avoid outdated global versions; use npx if there are conflicts.

Initialize the Subgraph Project

Create a new directory and initialize the boilerplate. This generates subgraph.yaml, schema.graphql, and src/mapping.ts. We're targeting Sepolia (chainId 11155111) and our ERC20 at 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.

Initialize the Subgraph

terminal
mkdir erc20-subgraph && cd erc20-subgraph
graph init --studio erc20-subgraph
# Follow the prompts:
# - Subgraph name: erc20-sepolia
# - Directory: .
# - Ethereum network: sepolia
# - Contract address: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
# - Contract ABI: (copy from Etherscan or Alchemy)

The init sets up the base manifest and downloads the ABI. Provide the exact address of the ERC20 contract deployed on Sepolia. If ABI is missing, export it from Sepolia Etherscan.

Define the GraphQL Schema

Analogy: The schema is the blueprint for your decentralized database, like a SQL table but queryable with GraphQL. We define Transfer with entities for from/to/value/block.

Complete GraphQL Schema

schema.graphql
type Transfer @entity {
  id: ID!
  from: Bytes! # hex address
  to: Bytes!
  value: BigInt!
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactionHash: Bytes!
}

type Account @entity {
  id: ID!
  totalTransfers: BigInt!
  totalSent: BigInt!
  totalReceived: BigInt!
}

type Token @entity {
  id: ID!
  totalSupply: BigInt!
  totalTransfers: BigInt!
}

Defines three entities: Transfer (primary events), Account (aggregates by address), Token (global metadata). Use BigInt for Solidity uint256. @entity enables automatic indexing.

Subgraph Manifest (subgraph.yaml)

subgraph.yaml
specVersion: 0.0.5
schema:
  file: ./schema.graphql

dataSources:
  - kind: ethereum/contract
    name: ERC20
    network: sepolia
    source:
      address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
      abi: ERC20
      startBlock: 5000000 # approx deployment block
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.8
      language: wasm/assemblyscript
      entities:
        - Transfer
        - Account
        - Token
      abis:
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

The manifest links the schema, ABI, and mappings. startBlock optimizes indexing (find it on Etherscan). apiVersion 0.0.8 supports the latest WASM features. Download ABI to ./abis/.

Write AssemblyScript Mappings

Mappings process blockchain events into entities. handleTransfer creates/updates Transfer, increments Account/Token counters. Use ctx.Token.createOrUpdate for idempotency.

Complete Mappings (mapping.ts)

src/mapping.ts
import { Transfer as TransferEvent } from "../generated/ERC20/ERC20";
import { Transfer, Account, Token } from "../generated/schema";
import { Address, BigInt, log } from "@graphprotocol/graph-ts";

export function handleTransfer(event: TransferEvent): void {
  let transferId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
  let transfer = new Transfer(transferId);
  transfer.from = event.params.from.toHex();
  transfer.to = event.params.to.toHex();
  transfer.value = event.params.value;
  transfer.blockNumber = event.block.number;
  transfer.blockTimestamp = event.block.timestamp;
  transfer.transactionHash = event.transaction.hash;
  transfer.save();

  let tokenId = "current";
  let token = Token.load(tokenId);
  if (token == null) {
    token = new Token(tokenId);
    token.totalSupply = BigInt.fromI32(0);
  }
  token.totalTransfers = token.totalTransfers.plus(BigInt.fromI32(1));
  token.save();

  let fromId = event.params.from.toHex();
  let fromAccount = Account.load(fromId);
  if (fromAccount == null) {
    fromAccount = new Account(fromId);
    fromAccount.totalSent = BigInt.fromI32(0);
    fromAccount.totalReceived = BigInt.fromI32(0);
    fromAccount.totalTransfers = BigInt.fromI32(0);
  }
  fromAccount.totalSent = fromAccount.totalSent.plus(event.params.value);
  fromAccount.totalTransfers = fromAccount.totalTransfers.plus(BigInt.fromI32(1));
  fromAccount.save();

  let toId = event.params.to.toHex();
  let toAccount = Account.load(toId);
  if (toAccount == null) {
    toAccount = new Account(toId);
    toAccount.totalSent = BigInt.fromI32(0);
    toAccount.totalReceived = BigInt.fromI32(0);
    toAccount.totalTransfers = BigInt.fromI32(0);
  }
  toAccount.totalReceived = toAccount.totalReceived.plus(event.params.value);
  toAccount.totalTransfers = toAccount.totalTransfers.plus(BigInt.fromI32(1));
  toAccount.save();
}

Generates a unique ID per log. load/create ensures atomic upserts. Increments aggregates for analytics (e.g., totalSent). Log with log.info for debugging. Test with real events post-5000000.

Generate Types and Build

terminal
graph codegen
graph build
ls generated/ # check erc20/ERC20.ts

Codegen generates TS types from ABI/schema (e.g., TransferEvent). Build compiles to WASM. Common error: malformed ABI; validate JSON.

Local Deployment with Docker

Local setup: Run graph-node to test without costs. Provide your Alchemy Sepolia RPC in docker-compose.yml.

Docker Compose for graph-node

docker-compose.yml
version: '3.6'
services:
  postgres:
    image: postgres
    environment:
      POSTGRES_DB: subgraph
      POSTGRES_USER: subgraph
      POSTGRES_PASSWORD: subgraph
    ports:
      - "5432:5432"
  graph-node:
    image: semaphoreui/graph-node:v0.38.0
    depends_on: [postgres]
    environment:
      postgres_host: postgres
      postgres_db: subgraph
      postgres_user: subgraph
      postgres_pass: subgraph
      ethereum: sepolia https://eth-sepolia.g.alchemy.com/v2/VOTRE_CLE_API
      GRAPH_LOG: info
    ports:
      - "8020:8020"
      - "8000:8000"
      - "8001:8001"
  indexer-agent:
    image: semaphoreui/indexer-agent:v0.6.0
    depends_on: [graph-node]
    environment:
      GRAPH_NODE: http://graph-node:8020
      INDEXER: http://indexer:80

Replace VOTRE_CLE_API with your Alchemy key. Ports: 8000/GraphQL, 8020/admin, 8001/query. Use up -d for background.

Deploy and Query Locally

terminal
docker-compose up -d
graph create-local --node http://localhost:8020/ erc20-sepolia
graph deploy --version-label v0.1.0 --node http://localhost:8020/ --ipfs http://localhost:5001 erc20-sepolia

# Query playground
curl -X POST -H 'Content-Type: application/json' --data '{"query": "{ transfers(first:5, orderBy: blockTimestamp, orderDirection: desc) { id from to value } }"}' http://localhost:8000/subgraphs/name/erc20-sepolia

Creates the local subgraph, deploys the WASM build. Query via curl or localhost:8001. Wait for sync (check logs with docker logs graph-node).

Deployment to Subgraph Studio

Authenticate and deploy to the hosted service, then migrate to decentralized.

Deploy to Studio

terminal
graph auth --studio VOTRE_ACCESS_TOKEN
# Get it from thegraph.com/studio

graph subgraph create --studio erc20-sepolia
graph deploy --studio erc20-sepolia

# Endpoint: https://api.studio.thegraph.com/query/XXXXXX/erc20-sepolia/v0.1.0

Token from Studio dashboard. First deploy indexes; subsequent ones update. Endpoint is publicly queryable. For decentralized: graph deploy --network mainnet after indexing.

Best Practices

  • Precise StartBlock: Use Etherscan to minimize sync time (e.g., 5000000 for our contract).
  • Paginated Indexing: Add skip/first in queries; limit to 1000 entities/page.
  • Denormalized Aggregates: Store totalSent in Account to avoid expensive joins.
  • Unit Tests: Use matchstick-as to mock events.
  • Monitoring: Watch fatal errors via Studio dashboard; retry with versioning.

Common Errors to Avoid

  • Incomplete ABI: Forgetting Transfer event → mappings crash. Always copy full ABI.
  • Non-unique ID: tx.hash + logIndex prevents duplicates during reorgs.
  • BigInt Overflow: AssemblyScript handles it natively; avoid JS Number.
  • Stuck Sync: RPC rate-limit; upgrade to Alchemy Pro or use snapshots.

Next Steps