Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through migrating EVM and Solana indexer setups that use .setGateway() (and optionally .setRpc()) over to Portal data sources. It works whether or not you have already migrated to a Portal for historical data.
Prefer to run this with Claude Code, Codex, or another AI coding agent? Install the migration skill in your squid’s directory:
npx skills add subsquid-labs/skills/squid-sdk/migrate-to-portal
Then open your agent in the same directory and ask it to migrate. The agent should find the skill; if if doesn’t, stop it and tell it where to look in plain English (e.g. at ./.agents/skills/migrate-to-portal if you installed the skill project-scoped for Claude).The skill detects your chain and follows the same steps as this guide. Skill source: subsquid-labs/skills/squid-sdk/migrate-to-portal.
If you’re reading this because you are been self-hosting any gateway-based squids after May 19, 2026 12:00 UTC and got an error requesting a gateway API key, be aware that migrating to Portals is the recommended, but not the only path available to you. The alternative is to register and access gateways with API keys.

Prerequisites

  • An existing squid based on
    • @subsquid/evm-processor on EVM: will typically use .setGateway() and .setRpcEndpoint(), in rare cases without either or with a .setPortal() - all of these cases are covered
    • @subsquid/solana-stream on Solana: using .setGateway() and/or .setRpc()
  • Node.js 22+ and npm installed
  • An SQD Portal URL for your dataset:
    • For public Portal: https://portal.sqd.dev/datasets/<slug> (find your slug on the networks page)
    • For a private Portal: get an URL from SQD or supply your own if self-hosting

Migration steps

Pick your chain.
Jump to: Reference templates · Common errors
1

Install new packages

Replace the old processor package with the new packages:
npm uninstall @subsquid/evm-processor @subsquid/archive-registry
npm i @subsquid/evm-stream @subsquid/evm-objects @subsquid/batch-processor @subsquid/logger
@subsquid/archive-registry and lookupArchive are deprecated. Remove the package and all lookupArchive(...) calls (we cover the data source replacement in Step 3).
If your handler imports decodeHex or assertNotNull from @subsquid/evm-processor, move those imports too. They are exported only by their utility packages now (both pulled in transitively by @subsquid/evm-stream and @subsquid/batch-processor, so no extra install needed):
-import {decodeHex, assertNotNull} from '@subsquid/evm-processor'
+import {decodeHex} from '@subsquid/util-internal-hex'
+import {assertNotNull} from '@subsquid/util-internal'
2

Switch the processor to a Portal data source

In src/processor.ts or src/main.ts, swap the processor imports and initialization:
-import {EvmBatchProcessor} from '@subsquid/evm-processor'
+import {DataSourceBuilder} from '@subsquid/evm-stream'
+import {augmentBlock} from '@subsquid/evm-objects'
+import {createLogger} from '@subsquid/logger'
-const processor = new EvmBatchProcessor()
-  .setGateway('https://v2.archive.subsquid.io/network/ethereum-mainnet')
-  .setRpcEndpoint('https://rpc.ankr.com/eth')
-  .setFinalityConfirmation(75)
+const dataSource = new DataSourceBuilder()
+  .setPortal('https://portal.sqd.dev/datasets/ethereum-mainnet')
   // ... .addLog(...), .setFields(...), etc.
+  .build()
RPC endpoint and finality confirmation settings are no longer needed.
Squids untouched since 2024 may use the pre-setGateway API:
.setDataSource({
  archive: lookupArchive('eth-mainnet'),
  chain: 'https://rpc.ankr.com/eth',
})
The destination is the same. Replace the whole block with .setPortal(...) and drop the lookupArchive import (@subsquid/archive-registry is gone, see Step 1).
The data types are now split across two packages: @subsquid/evm-stream exports FieldSelection; @subsquid/evm-objects exports the augmented block data types (BlockHeader, Block — previously BlockDataLog, Transaction, Trace, StateDiff). Three changes to be aware of:
  1. BlockData<F> is renamed to Block<F>. The single-block type previously called Block (just the header) is now BlockHeader<F>.
  2. The upstream DataHandlerContext lives in @subsquid/batch-processor. Its generic arguments are flipped (DataHandlerContext<Block, Store>, not <Store, Fields>) and it has only {store, blocks, isHead}. Add log (and _chain if you make RPC calls) yourself.
  3. The Fields type alias should be derived from the same fields constant you pass to .setFields().
-import {
-  BlockHeader,
-  DataHandlerContext,
-  EvmBatchProcessor,
-  Log as _Log,
-  Transaction as _Transaction,
-  BlockData as _BlockData,
-  FieldSelection,
-} from '@subsquid/evm-processor'
+import * as evmObjects from '@subsquid/evm-objects'
+import {DataSourceBuilder, FieldSelection} from '@subsquid/evm-stream'
+import type {DataHandlerContext as BaseDataHandlerContext} from '@subsquid/batch-processor'
+import type {Logger} from '@subsquid/logger'

 const fields = {
   log: { transactionHash: true },
 } satisfies FieldSelection

 export type Fields = typeof fields
-export type Block = BlockHeader<Fields>
-export type BlockData = _BlockData<Fields>
-export type Log = _Log<Fields>
-export type Transaction = _Transaction<Fields>
-export type ProcessorContext<Store> = DataHandlerContext<Store, Fields>
+export type Block = evmObjects.BlockHeader<Fields>
+export type BlockData = evmObjects.Block<Fields>
+export type Log = evmObjects.Log<Fields>
+export type Transaction = evmObjects.Transaction<Fields>
+export type DataHandlerContext<Store> = BaseDataHandlerContext<BlockData, Store> & {
+  log: Logger
+  // include `_chain` here if you make direct RPC calls (see the optional step below)
+}
Treat the .setPortal call in the same way as you would a .setGateway call - remove it alongside the processor initialization and set up a new one.
Previously, using a gateway data source only (without RPC) enabled the regime when the squid only consumes and processes finalized data. This introduces a significant delay between the data appearing on chain and in the indexer, but allows you to forward data to append-only destinations, which is not possible with real time data. To enable this regime in Portal-powered squids, explicitly disable hot blocks support in the target database, e.g.
const db = new TypeormDatabase({supportHotBlocks: false})
A data source streaming into such a target will automatically switch to ingesting from /finalized-stream instead of /stream.
3

Expand the field selection

Every field your handler reads must be listed in .setFields(). With EvmBatchProcessor, common fields like log.address, log.topics, log.data, block.timestamp and transaction.from/to/hash were merged in from a built-in default set. DataSourceBuilder fetches only the fields you list, and TypeScript will reject any unlisted access.
A minimum set typical for an EVM logs indexer:
const fields = {
  block: { timestamp: true },
  log: {
    address: true,
    topics: true,
    data: true,
    transactionHash: true, // and anything else you were already selecting
  },
} satisfies FieldSelection
Add transaction/trace/stateDiff entries as needed. TypeScript errors at compile time will point you at any field still missing.The top-level block.height and block.timestamp shortcuts are also gone. Read them from block.header.number and block.header.timestamp. block.header.height still exists but is @deprecated.
If your squid is from pre-@subsquid/[email protected] era, rename evmLog to log.
4

Update data requests

Rewrite data requests using the new where-include-range syntax:
 .addLog({
-  address: [USDC_CONTRACT_ADDRESS],
-  topic0: [usdcAbi.events.Transfer.topic],
-  transaction: true, // include the parent transaction
+  where: {
+    address: [USDC_CONTRACT_ADDRESS],
+    topic0: [usdcAbi.events.Transfer.topic],
+  },
+  include: {
+    transaction: true, // include the parent transaction
+  },
   range: { from: 6_082_465 },
 })
5

Update the run function

Replace processor.run() with the unified run function. Add the import, create a logger, and enrich the context with augmentBlock:
+import {run} from '@subsquid/batch-processor'

+const logger = createLogger('sqd:processor:mapping')

-processor.run(db, async (ctx) => {
+run(dataSource, db, async (simpleCtx) => {
+  const ctx = {
+    ...simpleCtx,
+    blocks: simpleCtx.blocks.map(augmentBlock),
+    log: logger,
+  }
   // ... handler body
 })
6

Add an RPC client

Optional Only do this if your batch handler makes direct RPC calls (contract state queries). Otherwise skip.Install the client, initialize an RpcClient, and enrich the batch context with a _chain field:
npm i @subsquid/rpc-client
import {RpcClient} from '@subsquid/rpc-client'

const rpcClient = new RpcClient({
  url: 'https://my_rpc_url',
  rateLimit: 100,
})
  const ctx = {
    ...simpleCtx,
    blocks: simpleCtx.blocks.map(augmentBlock),
    log: logger,
+    _chain: {
+      client: rpcClient
+    }
  }
If you keep a DataHandlerContext<Store> alias in src/processor.ts, extend it so _chain is part of the type your handlers see:
 export type DataHandlerContext<Store> = BaseDataHandlerContext<BlockData, Store> & {
   log: Logger
+  _chain: { client: RpcClient }
 }
Contract state queries then work as before:
const usdcContract = new usdcAbi.Contract(ctx, blocks[0].header, USDC_CONTRACT_ADDRESS)
const decimals = await usdcContract.decimals()
7

Smoke-test locally

Build and run the squid locally before deploying. If the first batch lands without TypeScript or missing-field runtime errors, you’re ready to deploy:
npm run build
npx squid-typeorm-migration apply
node lib/main.js
8

Re-sync the squid

We highly recommend that all squids migrated to Portal are re-synced. This allows you to make sure that everything works as expected for the whole length of the chain and catch any bugs early.If your squid is deployed to the Cloud, follow the zero-downtime update procedure:
  1. Deploy your squid into a new slot.
  2. Wait for it to sync, observing the improved data fetching.
  3. Assign your production tag to the new deployment to redirect the GraphQL requests there.
See the slots and tags guide for details.Can’t afford a re-sync? Simply re-deploy your squid without resetting its database.
A few canonical TypeScript errors map back to specific steps above. If you hit one, jump to the fix:
SymptomRoot causeFix
Property 'transactionHash' does not exist on type 'Log' (used log key)Forgot to list transactionHash: true under .setFields().logAdd transactionHash: true (and any other fields you read) under .setFields().log
Property 'height' does not exist on type 'BlockHeader' (or 'timestamp')Top-level block.height/block.timestamp shortcuts removedUse block.header.number / block.header.timestamp. block.header.height still works (deprecated) if .height is baked into entity columns and you want a literal rename.
'apiKey' does not exist in type 'GatewaySettings'You’re on evm-processor < 1.30.0 and tried to add apiKey before bumping the package.Either (a) migrate to evm-stream / .setPortal('...'): Portal needs no API key today. (b) Bump @subsquid/evm-processor to ^1.30.0 to get the apiKey field on GatewaySettings.
Module '"@subsquid/evm-processor"' has no exported member 'decodeHex'decodeHex is only exported by @subsquid/util-internal-hex nowSee Step 1 Install new packages
Module '"@subsquid/evm-processor"' has no exported member 'assertNotNull'assertNotNull is only exported by @subsquid/util-internal nowSame step
Module '"@subsquid/archive-registry"' has no exported member 'lookupArchive' (or Cannot find module)Package removed entirelyUninstall @subsquid/archive-registry and replace the whole data source block with .setPortal(...)
Object literal may only specify known properties, and 'evmLog' does not exist.setFields() key renamed in evm-processor@^1.21.0Rename evmLog to log in .setFields() (see Expand the field selection)
Property 'transactionHash' does not exist on type 'Log' (used evmLog key)evmLog key no longer projects transactionHash onto LogRename evmLog to log in .setFields()

Reference templates (EVM)

If you want to diff against a known-good shape, these are the canonical post-migration templates:

squid-evm-rt-template@with-logger

Portal data source, augmented context, no direct RPC calls

squid-evm-rt-template@with-rpc-client

Same as above plus an RpcClient wired into the context for state queries

Next steps (EVM)

Portal EVM API

Complete Portal API documentation for EVM

EVM examples

More examples of Portal API usage on EVM