Loaders Optimization (Removed in V3)
The handlerWithLoader API and the loaders flag in config.yaml were removed in HyperIndex V3. Preload Optimization is now always on — there is no flag to enable or disable it. This page is kept for historical context and to help V2 projects migrate.
What Were Loaders?
Loaders were a feature in early V2 versions of HyperIndex that significantly improved database access performance for event handlers.
They worked by implementing the Preload Optimization - loading required data upfront before processing events.
The preloaded data would then be available to event handlers through a loaderReturn object, eliminating the need for individual database queries during event processing.
In V2, handlers with loaders didn't have a Preload Phase and always ran once. In V3, every handler runs through the Preload Phase automatically, so the dedicated handlerWithLoader API and the loaders: config flag have both been removed.
The V2 shape looked like this (no longer accepted in V3):
// V2 only — removed in V3
ContractName.EventName.handlerWithLoader({
// The loader function runs before event processing starts
loader: async ({ event, context }) => {
// Load all required data from the database
// Return the data needed for event processing
return {}; // This will be available in the handler as loaderReturn
},
// The handler function processes each event with pre-loaded data
handler: async ({ event, context, loaderReturn }) => {
// Process the event using the data returned by the loader
},
});
How It Works in V3
In V3 the optimization is built in. See Preload Optimization - How It Works? for the full mechanics. The two-phase execution is identical to what loaders provided, just without a separate API.
For example, this is how a V2 loader is rewritten as a regular V3 handler — the only thing that changed is that the loader code now lives inline at the top of the handler:
// V2 — removed
ERC20.Transfer.handlerWithLoader({
loader: async ({ event, context }) => {
// Load sender and receiver accounts efficiently
const sender = await context.Account.get(event.params.from);
const receiver = await context.Account.get(event.params.to);
// Return the loaded data to the handler
return {
sender,
receiver,
};
},
handler: async ({ event, context, loaderReturn }) => {
const { sender, receiver } = loaderReturn;
// Process the transfer with the pre-loaded data
// No database lookups needed here!
},
});
// V3 — Preload Optimization is always on
import { indexer } from "envio";
indexer.onEvent(
{ contract: "ERC20", event: "Transfer" },
async ({ event, context }) => {
// Load sender and receiver accounts efficiently
const sender = await context.Account.get(event.params.from);
const receiver = await context.Account.get(event.params.to);
// To imitate the behavior of the loader,
// we can use `context.isPreload` to make next code run only once.
// Note: This is not required, but might be useful for CPU-intensive operations.
if (context.isPreload) {
return;
}
// Process the transfer with the pre-loaded data
},
);
If your project still has loaders: true or preload_handlers: true in config.yaml, remove both fields — V3 will reject them.