| name | indexer-handlers |
| description | Use when writing or editing event handlers. Handler registration, context API (entity CRUD, getWhere queries, chain, log), spread updates, indexer runtime API, and common pitfalls. |
| metadata | {"managed-by":"envio"} |
Handler Syntax & Core API
ESM Project
This is an ESM project ("type": "module" in package.json). Top-level await is available. Use import/export syntax, not require.
Modification Workflow
- After any change to
schema.graphql or config.yaml → run pnpm codegen
- After any change to TypeScript files → run
pnpm tsc --noEmit
- Once compilation succeeds → run
pnpm dev to catch runtime errors
Handler Registration
import { Contract } from "envio";
Contract.Event.handler(async ({ event, context }) => {
});
Handlers accept an optional 2nd argument — see indexer-wildcard and indexer-filters skills.
Context API
Entity Operations
const entity = await context.Entity.get(id);
const entity = await context.Entity.getOrThrow(id);
const entity = await context.Entity.getOrCreate({ id, ...defaults });
const list = await context.Entity.getWhere({ fieldName: { _eq: value } });
const list = await context.Entity.getWhere({ fieldName: { _gt: value } });
const list = await context.Entity.getWhere({ fieldName: { _lt: value } });
const list = await context.Entity.getWhere({ fieldName: { _gte: value } });
const list = await context.Entity.getWhere({ fieldName: { _lte: value } });
const list = await context.Entity.getWhere({ fieldName: { _in: [value1, value2] } });
const list = await context.Entity.getWhere({ fieldName: { _gte: min, _lte: max } });
const list = await context.Entity.getWhere({ fieldA: { _eq: a }, fieldB: { _eq: b } });
context.Entity.set(entity);
context.Entity.deleteUnsafe(id);
getWhere operators: _eq, _gt, _lt, _gte, _lte, _in. Multiple fields and operators combine with AND semantics. Only id and @index fields are queryable. See indexer-schema for @index syntax.
Context Properties
context.chain.id
context.chain.isRealtime
context.isPreload
context.log
context.effect(fn, input)
Spread Operator for Updates
Entities from context.Entity.get() are read-only. Always spread:
const entity = await context.Entity.get(id);
if (entity) {
context.Entity.set({ ...entity, field: newValue });
}
indexer Runtime API
import { indexer } from "envio";
indexer.name;
indexer.chainIds;
indexer.chains[1].id;
indexer.chains[1].name;
indexer.chains[1].startBlock;
indexer.chains[1].isRealtime;
indexer.chains[1].MyContract.name;
indexer.chains[1].MyContract.addresses;
indexer.chains[1].MyContract.abi;
Common Pitfalls
Entity IDs — prefer ${chainId}_${blockNumber}_${logIndex} as a unique ID:
const id = `${event.chainId}_${event.block.number}_${event.logIndex}`;
This is globally unique across chains and blocks. Use it as the default unless the entity is a singleton (e.g., a Token or Pool keyed by address).
Entity relationships — schema uses entity references; handlers use the _id suffix that codegen adds:
Optionals — string | undefined, not string | null
Decimal normalization — ALWAYS normalize when adding tokens with different decimals.
Schema & config — see indexer-schema and indexer-configuration skills for full reference.
If something is unclear, use the envio-docs skill to search and read the latest documentation.