Custom APIs
ENSDb (PostgreSQL)
For special use cases that go beyond what the ENS Omnigraph exposes — you can query the live onchain state of both ENSv1 and ENSv2 directly via SQL.
ENSDb is a bi-directional integration standard for any EnsDbWriter and EnsDbReader implementations to coordinate around the live unified onchain state of ENSv1 and ENSv2 in a carefully-crafted standardized data model within a PostgreSQL database. Because ENSDb builds on Postgres, you can use any language with a Postgres driver — TypeScript, Python, Rust, Go, and more.
Inspirations for what you can build with ENSDb
Section titled “Inspirations for what you can build with ENSDb”Analytics & Dashboards
CLIs & Developer Tools
Event-Based Engines
Data Pipelines
AI Agents
Example: fetch a Domain by canonical name
Section titled “Example: fetch a Domain by canonical name”Canonical fields (canonical_name, canonical_path, canonical_node, canonical_depth) are
populated on every Domain reachable from the canonical root, across both ENSv1 and ENSv2 — query
them uniformly without branching by type.
import { EnsDbReader } from "@ensnode/ensdb-sdk";import { eq } from "drizzle-orm";
const ensDbReader = new EnsDbReader(ensDbConnectionString, ensIndexerSchemaName);const { ensDb, ensIndexerSchema } = ensDbReader;
const [vitalik] = await ensDb .select() .from(ensIndexerSchema.domain) .where(eq(ensIndexerSchema.domain.canonicalName, "vitalik.eth"));SELECT * FROM ensindexer_0.domainsWHERE canonical_name = 'vitalik.eth';Example: count an address’s Domains by type
Section titled “Example: count an address’s Domains by type”import { count, eq } from "drizzle-orm";
const counts = await ensDb .select({ type: ensIndexerSchema.domain.type, count: count() }) .from(ensIndexerSchema.domain) .where(eq(ensIndexerSchema.domain.ownerId, "0xd8da6bf26964af9d7eed9e03e53415d37aa96045")) .groupBy(ensIndexerSchema.domain.type);SELECT type, count(*) FROM ensindexer_0.domainsWHERE owner_id = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'GROUP BY type;