Upgrading from v1

This guide explains how to upgrade your Starknet indexers from the old Apibara CLI experience to the new Apibara v2 experience.

At the time of writing (June 2025), Apibara v2 is the recommended version for new and existing applications.

Main changes

  • The underlying gRPC protocol and data types have changed. You can review all the changes on this page.
  • The old CLI has been replaced by a pure Typescript library. This means you can now leverage the full Node ecosystem (including Bun and Deno).
  • You can now extend indexers with plugins and hooks.

Migration

For this guide, we'll assume an indexer like the following:

indexer.ts
export const config = {
  streamUrl: "https://mainnet.starknet.a5a.ch",
  startingBlock: 800_000,
  network: "starknet",
  finality: "DATA_STATUS_ACCEPTED",
  filter: {
    header: {},
  },
  sinkType: "console",
  sinkOptions: {},
};

export default function transform(block) {
  return block;
}

Step 1: initialize the Node project

Initialize the project to contain a package.json file:

Terminal
npm init -y

Create the indexers/ folder where all the indexers will live:

Terminal
mkdir indexers

Add the dependencies needed to run the indexer. If you're using any external dependencies, make sure to add them.

Terminal
npm add --save apibara@next @apibara/protocol@next @apibara/starknet@next
added 325 packages, and audited 327 packages in 11s

73 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Step 2: initialize the Apibara project

Create a new file called apibara.config.ts in the root of your project.

apibara.config.ts
import { defineConfig } from "apibara/config";

export default defineConfig({});

Step 3: update the indexer

Now it's time to update the indexer.

  • Move the indexer to the indexers/ folder, ensuring tha the file name ends with .indexer.ts.
  • Wrap the indexer in a defineIndexer(StarknetStream)({ /* ... */ }) call. Notice that now the stream configuration and transform function live in the same configuration object.
  • startingBlock is now a BigInt.
  • streamUrl is the same.
  • finality is now simpler to type.
  • The filter object changed. Please refer to the filter documentation for more information.
  • sinkType and sinkOptions are gone.
  • The transform function now takes named arguments, with block containing the block data.

The following git diff shows the changes to the indexer at the beginning of the guide.

diff --git a/simple.ts b/indexers/simple.indexer.ts
index bb09fdc..701a494 100644
--- a/simple.ts
+++ b/indexers/simple.indexer.ts
@@ -1,15 +1,18 @@
-export const config = {
-    streamUrl: "https://mainnet.starknet.a5a.ch",
-    startingBlock: 800_000,
-    network: "starknet",
-    finality: "DATA_STATUS_ACCEPTED",
+import { StarknetStream } from "@apibara/starknet";
+import { defineIndexer } from "apibara/indexer";
+import { useLogger } from "apibara/plugins";
+
+export default defineIndexer(StarknetStream)({
+    streamUrl: "https://mainnet.starknet.a5a.ch",
+    startingBlock: 800_000n,
+    finality: "accepted",
     filter: {
-        header: {},
+        header: "always",
     },
-    sinkType: "console",
-    sinkOptions: {},
-};
-
-export default function transform(block) {
-    return block;
-}
\ No newline at end of file
+    async transform({ block }) {
+        const logger = useLogger();
+        logger.info(block);
+    },
+});
\ No newline at end of file

Step 4: writing data

In version 1, the indexer would write data returned by transform to a sink. Now, you use plugins to write data to databases like PostgreSQL or MongoDB.

Refer to the plugins documentation for more information.

Sink-specific instructions

Webhook

Depending on your use-case, you have two strategies to update your existing webhook sink script to v2:

  • Call the external webhook using the fetch API.
  • Inline the webhook script in your indexer.

In the first case, transform the block's data like in V1 and then call the fetch method.

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

import { transformBlock } from "../lib/helpers";

export default defineIndexer(StarknetStream)({
  streamUrl,
  finality: "accepted",
  startingBlock: 1_000_000n,
  filter: {
    events: [
      {
        address:
          "0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a",
      },
    ],
  },
  plugins: [],
  async transform({ block }) {
    const payload = transformBlock(block);

    // Make an HTTP POST request to the webhook URL
    const response = await fetch("https://example.org/webhook", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    // Handle the response if needed.
    if (!response.ok) {
      throw new Error(`Response status: ${response.status}`);
    }

    const result = await response.json();
  },
});

If you were using the "raw" mode for the webhook sink script, you also need to register a hook to call the webhook URL on invalidate messages.

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

export default defineIndexer(StarknetStream)({
  /* same as before */
  async transform({ block }) {
    /* ... */
  },
  hooks: {
    async "message:invalidate"({ message }) {
      const { cursor } = message;
      const response = await fetch("https://example.org/webhook", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          // orderKey is a BigInt, so convert it to a string to safely send it to the webhook
          invalidate: {
            orderKey: String(cursor.orderKey),
            uniqueKey: cursor.uniqueKey,
          }
        }),
      });

      // Handle the response if needed.
    },
  },
});

Some users found that they can implement the webhook script inline in their indexer. This results in a more efficient indexer that is easier to maintain and deploy.

Please refer to the installation and getting started page for more information.

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.

Cookie Notice

We use cookies to enhance your browsing experience.