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:
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.
@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
Replace the old processor package with the new packages:
npm uninstall @subsquid/evm-processor @subsquid/archive-registrynpm 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.
If your squid still uses the older `.setDataSource({archive, chain})` shape
Squids untouched since 2024 may use the pre-setGateway API:
The destination is the same. Replace the whole block with .setPortal(...) and drop the lookupArchive import (@subsquid/archive-registry is gone, see Step 1).
Have data & context type aliases exported alongside the processor object? Rewrite them here.
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 BlockData — Log, Transaction, Trace, StateDiff). Three changes to be aware of:
BlockData<F> is renamed to Block<F>. The single-block type previously called Block (just the header) is now BlockHeader<F>.
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.
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)+}
If your squid uses `.setPortal()` alongside `.setRpcEndpoint()` (Portal beta participants)
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.
If your squid is not using real time data (.setGateway without .setRpcEndpont)
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:
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,})
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 buildnpx squid-typeorm-migration applynode 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:
Deploy your squid into a new slot.
Wait for it to sync, observing the improved data fetching.
Assign your production tag to the new deployment to redirect the GraphQL requests there.
Use 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 now
See Step 1 Install new packages
Module '"@subsquid/evm-processor"' has no exported member 'assertNotNull'
assertNotNull is only exported by @subsquid/util-internal now
Same step
Module '"@subsquid/archive-registry"' has no exported member 'lookupArchive' (or Cannot find module)
Package removed entirely
Uninstall @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.0
Rename 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 Log
Remove the SolanaRpcClient import and the .setRpc({...}) call from your data source. Your project still compiles on your current @subsquid/solana-stream version after this edit; once you bump to ^1.x.x in the next step (where SolanaRpcClient and .setRpc() disappear), you avoid a broken intermediate state.
-import {DataSourceBuilder, SolanaRpcClient} from '@subsquid/solana-stream'+import {DataSourceBuilder} from '@subsquid/solana-stream'
const dataSource = new DataSourceBuilder() .setGateway('https://v2.archive.subsquid.io/network/solana-mainnet')- .setRpc({- client: new SolanaRpcClient({- url: process.env.SOLANA_NODE- })- })
2
Upgrade SDK packages
From your squid’s folder, upgrade every @subsquid/* package to its latest release:
(The SolanaRpcClient import and .setRpc({...}) call are already gone from Step 1.)
Have data & context type aliases exported alongside the processor object? Rewrite them here.
If your project re-exports Fields/Block/Transaction/Context aliases that handlers consume, re-derive them against the new packages:
-import {DataSourceBuilder, FieldSelection, Block as _Block, Transaction as _Transaction, Instruction as _Instruction} from '@subsquid/solana-stream'+import {DataSourceBuilder, FieldSelection} from '@subsquid/solana-stream'+import * as solanaObjects from '@subsquid/solana-objects'+import type {DataHandlerContext as BaseDataHandlerContext} from '@subsquid/batch-processor'+import type {Logger} from '@subsquid/logger' const fields = { block: {timestamp: true}, transaction: {signatures: true, err: true, accountKeys: true}, } satisfies FieldSelection export type Fields = typeof fields-export type Block = _Block<Fields>-export type Transaction = _Transaction<Fields>-export type Instruction = _Instruction<Fields>-export type Context = DataHandlerContext<Store, Fields>+export type BlockHeader = solanaObjects.BlockHeader<Fields>+export type Block = solanaObjects.Block<Fields>+export type Transaction = solanaObjects.Transaction<Fields>+export type Instruction = solanaObjects.Instruction<Fields>+export type DataHandlerContext<Store> = BaseDataHandlerContext<Block, Store> & {+ log: Logger+}
If your squid is not using real time data (.setGateway without .setRpcEndpont)
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.
4
Convert block heights to slot numbers
Solana datasets on Portal index by slot number, not block height. Replace any block-height literals (typically in .setBlockRange({from: ...})) with the corresponding slot:
tokenBalance.preMint / postMint are the #1 silent break on real Solana migrations. DEX squids that read these to recover swap-side mints will hit Property 'preMint' does not exist on type 'TokenBalance' until you add them to .setFields().tokenBalance. Pre-1.x @subsquid/solana-stream merged them in for free; the current release returns only the fields you list.
transaction.accountKeys was never in the old SDK’s defaults either — add it if you read the fee payer via tx.accountKeys[0].
Full set of fields the old `@subsquid/solana-stream` used to merge in for free
Object
Fields merged in by the old SDK
block
timestamp
transaction
signatures, err
instruction
programId, accounts, data, isCommitted
log
programId, kind, message
balance
pre, post
tokenBalance
the full pre*/post* family (preMint, postMint, preOwner, postOwner, preAmount, postAmount, preDecimals, postDecimals, preProgramId, postProgramId)
reward
lamports, rewardType
If you want to keep using the block height to stay compatible with your old code, request it explicitly. block.header.number (the slot) is always available, but height is not:
Add instruction/log/balance/reward entries the same way. TypeScript errors at compile time will point you at any field still missing from the selection.
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 buildnpx squid-typeorm-migration applynode 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:
Deploy your squid into a new slot.
Wait for it to sync, observing the improved data fetching.
Assign your production tag to the new deployment to redirect the GraphQL requests there.