Building indexers

Indexers are created using the defineIndexer higher-order function. This function takes a stream definition and returns a function to define the indexer.

The job of an indexer is to stream and process historical data (backfilling) and then switch to real-time mode. Indexers built using our SDK are designed to handle chain-reorganizations automatically. If, for any reason, you need to receive notifications about reorgs, you can define a custom message:invalidate hook to handle them.

By default, the indexer is stateless (restarts from the beginning on restart) and does not provide any storage. You can add persistence and storage by using one of the provided storage plugins.

Examples

The following examples show how to create indexers for the Beacon Chain, EVM (Ethereum), and Starknet.

Beacon Chain indexer

beaconchain.indexer.ts
import { BeaconChainStream } from "@apibara/beaconchain";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(BeaconChainStream)({ /* ... */  });

EVM (Ethereum) indexer

evm.indexer.ts
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(EvmStream)({ /* ... */  });

Starknet indexer

starknet.indexer.ts
import { StarknetStream } from "@apibara/starknet";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(StarknetStream)({ /* ... */  });

Indexer configuration

All indexers take the same configuration options.

  • streamUrlstring
    The URL of the DNA stream to connect to.
  • filterTFilter
    The filter to apply to the DNA stream. This argument is specific to the stream definition. You should refer to the chain's filter reference for the available options (see Beacon Chain, EVM (Ethereum), Starknet).
  • finality"finalized" | "accepted" | "pending"
    Receive data with the specified finality. Defaults to accepted.
  • startingCursor{ orderKey: bigint, uniqueKey?: string }
    The cursor to start the indexer from. Defaults to the genesis block. The orderKey represents the block number, and the uniqueKey represents the block hash (optional).
  • debugboolean
    Enable debug mode. This will print debug information to the console.
  • transform({ block, cursor, endCursor, finality, context }) => Promise<void>
    The transform function called for each block received from the DNA stream.
  • factory({ block, context }) => Promise<{ filter?: TFilter }>
    The factory function used to add data filters at runtime. Useful for creating indexers for smart contracts like Uniswap V2.
  • hooksobject
    The hooks to register with the indexer. Refer to the plugins & hooks page for more information.
  • pluginsarray
    The plugins to register with the indexer. Refer to the plugins & hooks page for more information.

The transform function

The transform function is invoked for each block received from the DNA stream. This function is where you should implement your business logic.

Arguments

  • blockTBlock
    The block received from the DNA stream. This is chain-specific (see Beacon Chain, EVM (Ethereum), Starknet).
  • cursor{ orderKey: bigint, uniqueKey?: string }
    The cursor of the block before the received block.
  • endCursor{ orderKey: bigint, uniqueKey?: string }
    The cursor of the current block.
  • finality"finalized" | "accepted" | "pending"
    The finality of the block.
  • contextobject
    The context shared between the indexer and the plugins.

The following example shows a minimal indexer that streams block headers and prints them to the console.

evm.indexer.ts
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(EvmStream)({
  streamUrl: "https://ethereum.preview.apibara.org",
  filter: {
    header: "always",
  },
  async transform({ block }) {
    const { header } = block;
    console.log(header);
  },
});

The factory function

The factory function is used to add data filters at runtime. This is useful for creating indexers for smart contracts that deploy other smart contracts like Uniswap V2 and its forks.

Arguments

  • blockTBlock
    The block received from the DNA stream. This is chain-specific (see Beacon Chain, EVM (Ethereum), Starknet).
  • contextobject
    The context shared between the indexer and the plugins.

The following example shows a minimal indexer that streams PairCreated events from Uniswap V2 to detect new pools, and then streams the pool's events.

uniswap-v2.indexer.ts
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(EvmStream)({
  streamUrl: "https://ethereum.preview.apibara.org",
  filter: {
    logs: [{ /* ... */ }],
  },
  async factory({ block }) {
    const { logs } = block;
    return { /* ... */ };
  },
  async transform({ block }) {
    const { header, logs } = block;
    console.log(header);
    console.log(logs);
  },
});
Last modified
Apibara

Apibara is the fastest platform to build production-grade indexers that connect onchain data to web2 services.

© 2025 GNC Labs Limited. All rights reserved.