HyperIndex V3 is currently in alpha. While we don't plan major API changes, some features may still undergo minor breaking changes and developer experience improvements.
Migrate to HyperIndex V3 Alpha
15 full months have passed since the official HyperIndex v2.0.0. Since then, we have shipped 32 minor releases and multiple patches with zero breaking changes to the documented API. We also received PRs from 6 external contributors, grew from 1 GitHub star to 456, and saw many big projects rely on HyperIndex.
HyperIndex V3 Alpha focuses on modernizing the codebase and laying the foundation for many more months of development. This guide walks you through upgrading from V2 to V3.
New Features
CommonJS → ESM
We migrated HyperIndex from CommonJS-only to ESM-only. This enables:
- Using the latest versions of libraries that have long since abandoned CommonJS support
- Top-level await in handler files
Top-Level Await
Thanks to the migration to ESM, you can now use await directly in handler and other files:
import { ERC20 } from "generated";
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
// Load data before registering handlers
const addressesFromServer = await loadWhitelistedAddresses();
ERC20.Transfer.handler(
async ({ event, context }) => {
// ... your handler logic
},
{
wildcard: true,
eventFilters: [
{ from: ZERO_ADDRESS, to: addressesFromServer },
{ from: addressesFromServer, to: ZERO_ADDRESS },
],
}
);
Automatic Handler Registration (src/handlers)
We introduced automatic registration of handler files located in src/handlers.
Previously, you needed to specify an explicit path to a handler file for every contract in config.yaml. Now you can remove all of the paths from config.yaml and simply move the files to src/handlers. You can name the files however you want, but we suggest using contract names and having a file per contract.
If you don't like src/handlers, use the handlers option in config.yaml to customize it.
The explicit handler field in config.yaml still works, so you don't need to change anything immediately.
RPC for Live Indexing
Built by an external contributor @cairoeth to allow specifying live mode for an RPC data source to embrace low-latency head tracking:
rpc:
- url: https://eth-mainnet.your-rpc-provider.com
for: live
In this case, the RPC won't be used for historical sync but will join the source selection logic when entering live indexing.
Chain State on Context
The Handler Context object provides chain state via the chain property:
ERC20.Approval.handler(async ({ context }) => {
console.log(context.chain.id); // 1 - The chain id of the event
console.log(context.chain.isLive); // true - Whether the event chain is indexing at the head
});
Indexer State & Config
As a replacement for the deprecated and removed getGeneratedByChainId, we introduce the indexer value. It provides nicely typed chains and contract data from your config, as well as the current indexing state, such as isLive and addresses. Use indexer either at the top level of the file or directly from handlers. It returns the latest indexer state.
With this change, we also introduce new official types: Indexer, EvmChainId, FuelChainId, and SvmChainId.
import { indexer } from "generated";
indexer.name; // "uniswap-v4-indexer"
indexer.description; // "Uniswap v4 indexer"
indexer.chainIds; // [1, 42161, 10, 8453, 137, 56]
indexer.chains[1].id; // 1
indexer.chains[1].startBlock; // 0
indexer.chains[1].endBlock; // undefined
indexer.chains[1].isLive; // false
indexer.chains[1].PoolManager.name; // "PoolManager"
indexer.chains[1].PoolManager.abi; // unknown[]
indexer.chains[1].PoolManager.addresses; // ["0x000000000004444c5dc75cB358380D2e3dE08A90"]
Conditional Event Handlers
Now it's possible to return a boolean value from the eventFilters function to disable or enable the handler conditionally.
ERC20.Transfer.handler(
async ({ event, context }) => {
// ... your handler logic
},
{
wildcard: true,
eventFilters: ({ chainId }) => {
// Skip all ERC20 on Polygon
if (chainId === 137) {
return false;
}
// Track all ERC20 on Ethereum Mainnet
if (chainId === 1) {
return true;
}
// Track only whitelisted addresses on other chains
return [
{ from: ZERO_ADDRESS, to: WHITELISTED_ADDRESSES[chainId] },
{ from: WHITELISTED_ADDRESSES[chainId], to: ZERO_ADDRESS },
];
},
}
);
Automatic Contract Configuration
Started automatically configuring all globally defined contracts. This fixes an issue where addContract crashed because the contract was defined globally but not linked for a specific chain. Now it's done automatically:
contracts:
- name: UniswapV3Factory
events: # ...
- name: UniswapV3Pool
events: # ...
chains:
- id: 1
start_block: 0
contracts:
- name: UniswapV3Factory
address: 0x1F98431c8aD98523631AE4a59f267346ea31F984
# UniswapV3Pool no longer needed here - auto-configured from global contracts
- id: 10
start_block: 0
contracts:
- name: UniswapV3Factory
address: 0x1F98431c8aD98523631AE4a59f267346ea31F984
# UniswapV3Pool no longer needed here - auto-configured from global contracts
ClickHouse Sink (Experimental)
We added experimental support for a ClickHouse Sink. Postgres still serves as the primary database, and you can additionally sink the entities to a ClickHouse database that is restart- and reorg-resistant.
Configure with environment variables: ENVIO_CLICKHOUSE_SINK_HOST, ENVIO_CLICKHOUSE_SINK_DATABASE, ENVIO_CLICKHOUSE_SINK_USERNAME, ENVIO_CLICKHOUSE_SINK_PASSWORD. Currently supported only on Dedicated Plan.
Do not run multiple Sinks to the same database at the same time.
HyperSync Source Improvements
Multiple updates on the HyperSync side to achieve smaller latency and less traffic:
- Server-Sent Events instead of polling to get updates about new blocks
- CapnProto instead of JSON for query serialization
- Cache for queries with repetitive filters - huge egress saving when indexing thousands of addresses
Fuel Block Handler Support
Block handlers are now supported for Fuel indexing.
Solana Support (Experimental)
HyperIndex now supports Solana with RPC as a source. This feature is experimental and may undergo minor breaking changes.
To initialize a Solana project:
pnpx envio@3.0.0-alpha.5 init svm
See the Solana documentation for more details.
pnpx envio init Improvements
- Removed language selection to prefer TypeScript by default
- Cleaned up templates to follow the latest good practices
- Added new templates to highlight HyperIndex features, starting with:
Feature: Factory Contract
Block Handler Only Indexers
Now it's possible to create indexers with only block handlers. Previously, it was required to have at least one event handler for it to work. The contracts field became optional in config.yaml.
Flexible Entity Fields
We no longer have restrictions on entity field names, such as type and others. Shape your entities any way you want. There are also improvements in generating database columns in the same order as they are defined in the schema.graphql.
Unordered Multichain Mode by Default
Unordered multichain mode is now the default behavior. This provides better performance for most use cases. If you need ordered multichain behavior, you can explicitly set multichain: ordered in your config.
Preload Optimization by Default
Preload optimization is now enabled by default, replacing the previous loaders and preload_handlers options. This improves historical sync performance automatically.
TUI Improvements
We gave our TUI some love, making it look more beautiful and compact. It also consumes fewer resources, shares a link to the Hasura playground, and dynamically adjusts to the terminal width.

Breaking Changes
Node.js & Runtime
- Node.js 22 is now the minimum required version, while 24 is the recommended version
- Changes in handler files don't trigger codegen on
pnpm dev
Handler API Changes
- Removed
experimental_createEffectin favor ofcreateEffect - Renamed transaction field
kindtotype - For block handlers:
block.chainIdis removed in favor ofcontext.chain.id Addresstype changed fromstringto`0x${string}`- Removed
transaction.chainIdfrom field selection — usecontext.chain.idorevent.chainIdinstead
config.yaml Changes
- Renamed
networkstochains - Renamed
confirmed_block_thresholdtomax_reorg_depth - Removed
unordered_multichain_modeflag, replaced withmultichain: ordered | unordered(default:unordered) - Removed
loadersoption (now always enabled via Preload Optimization) - Removed
preload_handlersoption (now always enabled) - Removed
preRegisterDynamicContractsoption
Environment Variable Changes
- Removed
UNSTABLE__TEMP_UNORDERED_HEAD_MODEenvironment variable - Removed
UNORDERED_MULTICHAIN_MODEenvironment variable - Removed
MAX_BATCH_SIZEenvironment variable (usefull_batch_sizein config.yaml instead)
Generated Code Changes
- Removed
chaintype in favor ofChainId(now a union type instead of a number) - Removed internal
ContractTypeenum (allows longer contract names) - Removed
getGeneratedByChainId(useindexervalue instead)
Metrics Changes
- Renamed
chain_block_heightPrometheus metric toenvio_indexing_known_height
Migration Guide
Step 0: Prepare on V2 (Recommended)
Before upgrading to V3, we recommend preparing your project while still on V2:
- Upgrade to v2.32.6 and enable Preload Optimization:
# config.yaml
preload_handlers: true
-
If you were using loaders, migrate them to Preload Optimization following the Migrating from Loaders guide.
-
Verify your indexer works correctly with
pnpm devbefore proceeding to V3.
This step ensures a smoother migration by validating Preload Optimization works with your handlers before the V3 upgrade.
Step 1: Update Dependencies
Node.js
Update Node.js to version 22 or higher.
package.json
Update your package.json with the following changes:
{
"type": "module",
"engines": {
"node": ">=22.0.0"
},
"dependencies": {
"envio": "3.0.0-alpha.5"
},
"devDependencies": {
"typescript": "^5.7.3"
}
}
Adding "type": "module" is required for V3. Without it, your project will fail to start due to ESM import errors.
If you use testing with Mocha:
Remove ts-mocha and ts-node, then install tsx:
pnpm remove ts-mocha ts-node
pnpm add -D tsx@4.21.0
Update your test script in package.json:
{
"scripts": {
"mocha": "tsc --noEmit && NODE_OPTIONS='--no-warnings --import tsx' mocha --exit test/**/*.ts"
}
}
If you use ts-node for start script:
Replace with:
{
"scripts": {
"start": "envio start"
}
}
Step 2: Update tsconfig.json
Update your tsconfig.json to support ESM:
{
/* For details: https://www.totaltypescript.com/tsconfig-cheat-sheet */
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
/* For running Envio: */
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
/* Code doesn't run in the DOM: */
"lib": ["es2022"]
}
}
This includes additional strictness options like verbatimModuleSyntax and noUncheckedIndexedAccess. You can disable them to simplify the migration.
Step 3: Update config.yaml
Rename networks to chains:
# Before
networks:
- id: 1
contracts:
- name: MyContract
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)
# After
chains:
- id: 1
contracts:
- name: MyContract
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)
Update multichain mode (if applicable):
If you had unordered_multichain_mode: true, remove it — this is now the default. If you need ordered multichain behavior, explicitly set:
multichain: ordered
Rename config options:
confirmed_block_threshold→max_reorg_depth
Remove deprecated options:
Remove the following options from your config if present:
loaders— now always enabled via Preload Optimizationpreload_handlers— now always enabledpreRegisterDynamicContracts— no longer neededunordered_multichain_mode— replaced withmultichainoption
New option for batch size:
If you were using MAX_BATCH_SIZE environment variable, use the new config option instead:
full_batch_size: 5000
Automatic Handler Registration (optional):
Optionally move your handler files to src/handlers/ and remove the explicit handler paths from config.yaml.
Step 4: Update Environment Variables
Remove these deprecated environment variables if present:
UNSTABLE__TEMP_UNORDERED_HEAD_MODEUNORDERED_MULTICHAIN_MODEMAX_BATCH_SIZE— usefull_batch_sizein config.yaml instead
Step 5: Update Handler Code
Rename deprecated APIs:
| V2 (Deprecated) | V3 |
|---|---|
experimental_createEffect | createEffect |
block.chainId (in block handlers) | context.chain.id |
transaction.kind | transaction.type |
chain type | ChainId |
transaction.chainId | context.chain.id or event.chainId |
Removed APIs:
getGeneratedByChainId— use theindexer.chains[chainId]instead (see Indexer State & Config)
Step 6: Test Your Migration
After making all changes, run codegen and start your indexer:
pnpm envio codegen
pnpm dev
Quick Migration Checklist
Prepare (on V2):
- Upgrade to
envio@2.32.6 - Enable
preload_handlers: truein config.yaml - Migrate from loaders if applicable (guide)
- Verify indexer works with
pnpm dev
Dependencies:
- Update Node.js to >=22
- Add
"type": "module"topackage.json← Required for V3! - Update
enviodependency to3.0.0-alpha.5 - Update
engines.nodeto>=22.0.0inpackage.json - Update
tsconfig.jsonfor ESM support - Replace
ts-mocha/ts-nodewithtsxif using tests
config.yaml:
- Rename
networkstochains - Rename
confirmed_block_thresholdtomax_reorg_depth - Remove
unordered_multichain_mode(now default) - Remove
loadersandpreload_handlersoptions - Remove
preRegisterDynamicContractsoption
Environment Variables:
- Remove
UNSTABLE__TEMP_UNORDERED_HEAD_MODE - Remove
UNORDERED_MULTICHAIN_MODE - Remove
MAX_BATCH_SIZE(usefull_batch_sizein config.yaml)
Handler Code:
- Replace
experimental_createEffectwithcreateEffect - Replace
block.chainIdwithcontext.chain.idin block handlers - Replace
transaction.kindwithtransaction.type - Update usage of
chaintype toChainId - Replace
getGeneratedByChainIdwithindexer.chains[chainId] - Update code expecting
Addresstype to bestring(now`0x${string}`) - Replace
transaction.chainIdwithcontext.chain.idorevent.chainId
Verify:
- Run
pnpm envio codegenandpnpm devto verify
Getting Help
If you encounter any issues during migration, join our Discord community for support.
Release Notes
For detailed release notes, see: