Skip to main content
Use this page when the indexer is already writing rows and you want to consume them from your app.

Two Common Paths

PathBest for
Query the tables directlyApp-specific read paths, joins, dashboards, and custom SQL logic
Mount generated routesFast internal APIs over stream tables with pagination and filtering

Direct Table Queries

Export the stream tables from your schema package, then query them with Drizzle like any other app table.
import { desc, eq } from "drizzle-orm";
import { db } from "./db";
import { tokenTransferEvents, tokenAccountsTable } from "./schema";

const recentTransfers = await db
  .select()
  .from(tokenTransferEvents)
  .where(eq(tokenTransferEvents.dest, "ta..."))
  .orderBy(desc(tokenTransferEvents.slot))
  .limit(20);

const ownerBalances = await db
  .select()
  .from(tokenAccountsTable)
  .where(eq(tokenAccountsTable.owner, "ta..."));

Generated Routes

If you mounted mountStreamRoutes(...), each stream gets a generated route family under your chosen prefix. Typical uses:
  • list recent token transfer rows
  • filter token accounts by mint or owner
  • expose stream-backed data to internal tools quickly
The available query filters come from the stream’s api.filters configuration.

When To Use Generated Routes

Generated routes are a good fit when:
  • you want a fast internal API over indexer tables
  • the stream schema is already close to the response shape you need
  • pagination and simple filters are enough
Write custom app routes instead when:
  • you need joins across multiple indexed tables
  • you need auth, aggregation, or business-specific response shapes
  • you want non-stream resources mixed into the same endpoint

Practical Recommendation

For most apps:
  1. query tables directly inside the backend for core product endpoints
  2. use generated routes for tooling, admin views, or low-friction internal APIs