Migration guide

Migration guide


This release changes the location of database tables when using both SQLite and Postgres.

It does not require any changes to your application code, and does not bust the sync cache for SQLite or Postgres.

Please read the new docs on direct SQL for a detailed overview.


Ponder now uses the .ponder/sqlite/public.db file for indexed tables. Before, the tables were present as views in the .ponder/sqlite/ponder.db. Now, the.ponder/sqlite/ponder.db file is only used internally by Ponder.


Ponder now creates a table in the public schema for each table in ponder.schema.ts. Before, Ponder created them as views in the ponder schema.

Isolation while running multiple Ponder instances against the same database also works differently. Before, Ponder used a schema with a pseudorandom name if the desired schema was in use. Now, Ponder will fail on startup with an error if it cannot acquire a lock on the desired schema.

This also changes the zero-downtime behavior on platforms like Railway. For more information on how this works in 0.4, please reference:

Postgres table cleanup

After upgrading to 0.4.x, you can run the following Postgres SQL script to clean up stale tables and views created by 0.3.x Ponder apps.

Note: This script could obviously be destructive, so please read it carefully before executing.

DO $$
    view_name TEXT;
    schema_name_var TEXT;
    -- Drop all views from the 'ponder' schema
    FOR view_name IN SELECT table_name FROM information_schema.views WHERE table_schema = 'ponder'
        EXECUTE format('DROP VIEW IF EXISTS ponder.%I CASCADE', view_name);
        RAISE NOTICE 'Dropped view "ponder"."%"', view_name;
    -- Drop the 'ponder_cache' schema
    RAISE NOTICE 'Dropped schema "ponder_cache"';
    -- Find and drop any 'ponder_instance_*' schemas
    FOR schema_name_var IN SELECT schema_name AS schema_name_alias FROM information_schema.schemata WHERE schema_name LIKE 'ponder_instance_%'
        EXECUTE format('DROP SCHEMA IF EXISTS %I CASCADE', schema_name_var);
        RAISE NOTICE 'Dropped schema "%"', schema_name_var;
END $$;


No breaking API changes.

Moved SQLite directory

Note: This release busted the SQLite sync cache.

The SQLite database was moved from the .ponder/store directory to .ponder/sqlite. The old .ponder/store directory will still be used by older versions.

Moved Postgres sync tables

Similar to SQLite, the sync tables for Postgres were moved from the public schema to ponder_sync. Now, Ponder does not use the public schema whatsoever.

This change did NOT bust the sync cache; the tables were actually moved. This process emits some WARN-level logs that you should see after upgrading.


Replaced p.bytes() with p.hex()

Removed p.bytes() in favor of a new p.hex() primitive column type. p.hex() is suitable for Ethereum addresses and other hex-encoded data, including EVM bytes types. p.hex() values are stored as bytea (Postgres) or blob (SQLite). To migrate, replace each occurence of p.bytes() in ponder.schema.ts with p.hex(), and ensure that any values you pass into hex columns are valid hexadecimal strings. The GraphQL API returns p.hex() values as hexadecimal strings, and allows sorting/filtering on p.hex() columns using the numeric comparison operators (gt, gte, le, lte).

Cursor pagination

Updated the GraphQL API to use cursor pagination instead of offset pagination. Note that this change also affects the findMany database method. See the GraphQL pagination docs for more details.



  • In general, ponder.config.ts now has much more static validation using TypeScript. This includes network names in contracts, ABI event names for the contract event and factory options, and more.
  • The networks and contracts fields were changed from an array to an object. The network or contract name is now specified using an object property name. The name field for both networks and contracts was removed.
  • The filter field has been removed. To index all events matching a specific signature across all contract addresses, add a contract that specifies the event field without specifying an address.
  • The abi field now requires an ABI object that has been asserted as const (cannot use a file path). See the ABIType documentation for more details.


  • The schema definition API was rebuilt from scratch to use a TypeScript file ponder.schema.ts instead of schema.graphql. The ponder.schema.ts file has static validation using TypeScript.
  • Note that it is possible to convert a schema.graphql file into a ponder.schema.ts file without introducing any breaking changes to the autogenerated GraphQL API schema.
  • Please see the design your schema guide for an overview of the new API.

Indexing functions

  • event.params was renamed to event.args to better match Ethereum terminology norms.
  • If a contract uses the event option, only the specified events will be available for registration. Before, all events in the ABI were available.
  • context.models was renamed to context.db
  • Now, a read-only Viem client is available at context.client. This client uses the same transport you specify in ponder.config.ts, except all method are cached to speed up subsequent indexing.
  • The context.contracts object now contains the contract addresses and ABIs specified inponder.config.ts, typed as strictly as possible. (You should not need to copy addresses and ABIs around anymore, just use context.contracts).
  • A new object was added which contains the network name and chain ID that the current event is from.

Multi-chain indexing

  • The contract network field ponder.config.ts was upgraded to support an object of network-specific overrides. This is a much better DX for indexing the same contract on multiple chains.
  • The options that you can specify per-network are address, event, startBlock, endBlock, and factory.
  • When you add a contract on multiple networks, Ponder will sync the contract on each network you specify. Any indexing functions you register for the contract will now process events across all networks.
  • The object is typed according to the networks that the current contract runs on, so you can write network-specific logic like if ( === “optimism”) { …


  • Ponder now uses Vite to transform and load your code. This means you can import files from outside the project root directory.
  • Vite’s module graph makes it possible to invalidate project files granularly, only reloading the specific parts of your app that need to be updated when a specific file changes. For example, if you save a change to one of your ABI files, ponder.config.ts will reload because it imports that file, but your schema will not reload.
  • This update also unblocks a path towards concurrent indexing and granular caching of indexing function results.