Understanding Generated Indexing Files
Overview
In V3, the local generated package is gone. Code generation now writes a single ambient declaration file at .envio/types.d.ts (git-ignored) and wires it into your project through a small envio-env.d.ts file at the project root. Everything you used to import from generated is now exported from the envio package.
These generated declarations form the type-level backbone of your blockchain indexer, translating your configuration, schema, and event handlers into the strongly-typed runtime values exposed by envio.
Important: The contents of
.envio/should never be manually edited. Any changes will be overwritten the next time code generation runs.
What V3 Emits
| File / location | Purpose |
|---|---|
.envio/types.d.ts | Ambient TypeScript declarations describing your contracts, events, entities, enums, and chains. |
envio-env.d.ts (root) | Tiny shim that references .envio/types.d.ts so the compiler picks it up. |
.envio/cache/ (optional) | Local cache of Effect API results, populated via the dev console. |
The generated/ directory used by V2 (with ReScript sources, JS shims, and a per-project package.json) is no longer produced.
Purpose of Generated Files
Generated files serve several critical functions:
- Type-Safe Data Access - They provide strongly-typed interfaces to interact with your defined entities through
envio. - Event Processing - They describe each contract's events so
indexer.onEvent({ contract, event }, ...)is fully type-checked. - Database Interactions - They generate the entity types and helper signatures used by
context.<Entity>andindexer.<Entity>. - Runtime Orchestration - They feed into the
indexervalue (chains, contracts, entities) that orchestrates indexing.
Real-World Example: Uniswap V4 Indexer
Let's examine how specific elements from a real Uniswap V4 indexer translate into generated declarations.
From Schema to Generated Types
For a schema entity like this:
type Pool {
id: ID!
chainId: BigInt!
currency0: String!
currency1: String!
fee: BigInt!
tickSpacing: BigInt!
hooks: String!
numberOfSwaps: BigInt! @index
createdAtTimestamp: BigInt!
createdAtBlockNumber: BigInt!
}
The codegen process emits a TypeScript type you can import from envio:
import type { Entity } from "envio";
// Equivalent to importing the `Pool` named type directly.
type Pool = Entity<"Pool">;
// Shape of the generated entity:
// {
// id: string;
// chainId: bigint;
// currency0: string;
// currency1: string;
// fee: bigint;
// tickSpacing: bigint;
// hooks: string;
// numberOfSwaps: bigint;
// createdAtTimestamp: bigint;
// createdAtBlockNumber: bigint;
// }
You read and write Pool entities through the type-safe context and indexer APIs:
// Inside a handler
const pool = await context.Pool.get(id);
context.Pool.set({ id, chainId, currency0, currency1, fee, tickSpacing, hooks, numberOfSwaps, createdAtTimestamp, createdAtBlockNumber });
// Inside a test or script
await indexer.Pool.set({ /* ... */ });
const stored = await indexer.Pool.getOrThrow(id);
From Config to Generated Event Handlers
Given a contract event in config.yaml:
contracts:
- name: PoolManager
events:
- event: Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee)
Codegen widens the indexer.onEvent overloads so that the following call is fully typed end-to-end (event params, return type, context.chain, etc.):
import { indexer } from "envio";
import type { EvmEvent } from "envio";
indexer.onEvent(
{ contract: "PoolManager", event: "Swap" },
async ({ event, context }) => {
const { id, sender, amount0, amount1, sqrtPriceX96, liquidity, tick, fee } = event.params;
// ...
},
);
// The full event payload type is also available as a generic:
type PoolManagerSwapEvent = EvmEvent<"PoolManager", "Swap">;
type PoolManagerSwapBlock = EvmEvent<"PoolManager", "Swap">["block"];
From Multi-Chain Config to Generated Chain Handlers
Your config has multiple chains:
chains:
- id: 1 # Ethereum Mainnet
# ...
- id: 10 # Optimism
# ...
- id: 42161 # Arbitrum
# ...
Codegen turns the chain set into a literal ChainId union and exposes per-chain helpers under indexer.chains:
import { indexer } from "envio";
import type { ChainId } from "envio";
// ChainId is `1 | 10 | 42161`
const mainnet = indexer.chains[1];
const optimism = indexer.chains[10];
const arbitrum = indexer.chains[42161];
// `chain.id` is also typed inside handler/where callbacks:
indexer.onBlock(
{
name: "Heartbeat",
where: ({ chain }) => {
// chain.id is narrowed to the configured ChainId union
return chain.id === 1;
},
},
async ({ block, context }) => {
// context.chain.id is typed as well
},
);
getGeneratedByChainId(...) from V2 has been replaced by indexer.chains[chainId].
When to Run Code Generation
You should run code generation using the Envio CLI whenever you:
pnpm envio codegen
Codegen should be run after:
- Modifying your
config.yamlfile - Changing your GraphQL schema
- Adding or updating event handlers
- Switching to a new contract or ABI
- After pulling changes from version control
Note: changes to handler files in V3 no longer trigger automatic codegen on
pnpm dev.
Troubleshooting Generation Errors
When code generation fails, the errors typically point to issues in your setup files. Here are common error patterns and their solutions:
Configuration Errors
Error messages containing Config validation failed typically mean there's an issue in your config.yaml file:
- Check for syntax errors in YAML formatting
- Verify that all required fields are present
- Ensure contract addresses are in the correct format
- Confirm that referenced chains are valid
For example, if you see an error about invalid chain IDs, check that all chain IDs in your config are valid:
chains:
- id: 1 # Valid Ethereum mainnet
- id: 10 # Valid Optimism
- id: 999 # Might be invalid if this chain ID isn't recognized
Schema Errors
Errors mentioning Schema parsing error point to issues in your GraphQL schema:
- Check for invalid GraphQL syntax
- Ensure entity names match those referenced in handlers
- Verify that relationships between entities are properly defined
- Check for unsupported types or directives
For example, if you're using the @index directive as in your Pool entity's numberOfSwaps field, make sure it's correctly placed:
type Pool {
id: ID!
numberOfSwaps: BigInt! @index # Correct placement of @index directive
}
Handler Errors
If you see Handler validation failed errors:
- Check that handler function signatures match expected patterns
- Ensure all referenced entities exist in your schema
- Verify proper import syntax for entities and contract events (everything comes from
envio)
Relationship with Setup Files
The generated declarations directly reflect the structure defined in your setup files:
- config.yaml → Determines which chains, contracts, and events are indexed
- schema.graphql → Defines the entities and relationships that are generated
- handlers in
src/handlers/→ Provide the business logic that the generated types describe
Best Practices
- Never modify generated files directly - Always change the source files
- Run codegen before starting your indexer - Ensure all declarations are up to date
- Check error messages carefully - They often pinpoint issues in your setup files
- Commit
envio-env.d.tsbut ignore.envio/- The shim is part of the project; the generated artifacts are not.
Summary
Generated declarations form the critical bridge between your indexing specifications and the actual runtime execution. While you shouldn't modify them directly, understanding their structure and purpose can help you debug issues and optimize your indexing process.
If you encounter persistent errors related to generated files, ensure your configuration, schema, and handlers follow Envio's best practices, or contact support for assistance.