Getting started
Migrate a subgraph

Migrate a Graph Protocol subgraph

Create a Ponder project

pnpm create ponder

First, enter a project name. Then select the Subgraph ID template option and paste in your subgraph's deployment ID:

Subgraph ID screenshot

MakerDAO's subgraph on the Graph Protocol's hosted service.

The newly generated Ponder app will copy the subgraph's schema.graphql as well as the addresses, ABIs, and start blocks for each event source defined in subgraph.yaml. Then, it will create a project directory, install dependencies, and initialize a git repository.

Start the development server

Just like Next.js and Vite, Ponder has a development server that automatically reloads when you save changes in any project file. It also prints console.log statements and errors encountered while running your code.

First, cd into your project directory, then start the server.

pnpm dev

Add an RPC URL

Ponder fetches data using the standard Ethereum RPC API. To get started, you'll need an RPC URL from a provider like Alchemy or Infura.

Open .env.local and paste in RPC URLs for any networks that your project uses.

Each RPC URL environment variable is named PONDER_RPC_URL postfixed with the chain ID (e.g. PONDER_RPC_URL_8453 for Base Mainnet):


Write indexing functions

Open any file in the src/ directory. Ponder indexing functions are very similar to subgraph mapping functions, with some key differences. You will need to refactor your code, but because the local dev server has hot reloading, you'll get instant feedback on your changes.

See tips for migrating mapping functions for more details.

Query the GraphQL API

As you migrate your indexing functions and start inserting data, open the GraphiQL interface at http://localhost:42069/graphql to explore your GraphQL API locally. Any changes you make to your ponder.schema.ts file will be reflected here.

Tips for migrating mapping functions

Functions run in Node.js, not WebAssembly

You can import NPM packages, debug with console.log, and use normal JavaScript types like string and number.

Database models & contracts are injected, not imported

In a Graph Protocol subgraph mapping file, entity models and contract factories are imported:

mapping.ts (Graph Protocol)
import { Token, Wallet } from "../generated/schema";
import { MyNftContract } from "../generated/MyNftContract/MyNftContract";
export function handleTransfer(event) {
  // Get an entity object
  const token = Token.load(;
  // Bind and call a contract
  const tokenUri = MyNftContract.bind("0x137...01a2").tokenURI(;
  // ...

With Ponder, these objects are injected as properties of context.

import { ponder } from "@/generated";
ponder.on("MyNftContract:Transfer", async ({ event, context }) => {
  // Get a database record
  const token = await context.db.Token.findUnique({ id: });
  // Call a contract read function
  const tokenUri = await context.client.readContract({
    abi: context.contracts.MyNftContract.abi,
    address: context.contracts.MyNftContract.address,
    functionName: "tokenURI",
    args: [event.args.tokenId],
  // ...

See the Create & update records and Read contract data guides for more details.