ENS Omnigraph API
The ENS Omnigraph API is the GraphQL API over the indexed Unigraph data model. It’s a single, polymorphic schema over both ENSv1 and ENSv2 — write your query once and get correct, typed results regardless of which protocol version a given Domain lives in.

The Omnigraph is delivered by ENSApi on top of the indexed Unigraph in ENSDb. It follows the Relay specification, abstracts away the most common ENS-protocol footguns, and exposes enough of the underlying protocol for builders who need to go deep.
One unified API over ENSv1 + ENSv2
Section titled “One unified API over ENSv1 + ENSv2”Both ENSv1 and ENSv2 Domains are indexed concurrently and exposed through the same schema. When ENSv2 launches in Summer 2026, the two protocol versions coexist — and the Omnigraph keeps your app working against both, at the same time, with no code changes.
Addressing a Domain by name (domain(by: { name: "vitalik.eth" })) prefers the ENSv2 entity when one exists, and falls back to the ENSv1 entity otherwise. Set-returning queries (domains(where: { name: ... })) return both protocol versions, unless explicitly filtered with version: ENSv1 or version: ENSv2.
What is a Namegraph?
Section titled “What is a Namegraph?”A Namegraph is the native data model of ENSv2: it represents names not as a flat mapping of namehashes (as in ENSv1), but as a graph of Registry → Domain → Registry → Domain → …. This graph may be cyclic, and within the ENSv2 protocol an unbounded number of disjoint (not connected) Namegraphs may/will exist. Within the unified ENS protocol (v1+v2) there will exist many Namegraphs, at the very least those headed by the ENSv1 Root Registry, Basenames, Lineanames, 3DNS, and the ENSv2 Root Registry.
What is the Unigraph?
Section titled “What is the Unigraph?”The Unigraph is the entire collection of these disjoint namegraphs, indexed and stitched together using ENS Forward Resolution semantics, into a single navigable Namegraph. The result is a single, uniform data model — every ENSv1 query is the same shape as every ENSv2 query. Navigating the Namegraph from "eth" down to "vitalik.eth" and beyond looks identical regardless of whether the underlying entities are ENSv1 or ENSv2.
The unigraph plugin in ENSIndexer is what builds this unified model. It’s also where multichain coverage lives: Basenames (.base.eth), Lineanames (.linea.eth), and 3DNS names (.box) are all materialized into the same Namegraph as mainnet .eth, so a single query covers all indexable names in one shot.
Canonicality
Section titled “Canonicality”Given that a Domain entity (say, the sub in sub.example.eth) can be reached by infinitely many aliases (for example, sub.other.eth), it becomes important to determine a canonical reference to the Domain — this is the Canonical Name. Canonicality is also connected to nameability; if a Domain exists on-chain but isn’t eventually connected to the Root Registry somehow, it doesn’t have a name!
Within the Omnigraph API the complexity of this is reduced, and all Canonical Domains (those with names) are queryable, searchable, and addressable by said name. Domains that are not canonical are still referenceable by id (eg. domain(by: { id: DomainId! })).
Canonical Domains have a Domain.canonical field hosting the canonicality-derived fields such as name, node, depth (i.e. 2 for vitalik.eth), and path (i.e. [Domain("eth"), Domain("vitalik.eth")]).
Stable IDs vs. Namegraph addressing
Section titled “Stable IDs vs. Namegraph addressing”Every entity in the Omnigraph has an id — a nominally-typed, stable reference to a specific on-chain entity (DomainId, RegistryId, RegistrationId, etc.). When you already know the entity you mean, address it by id: domain(by: { id: "..." }) always returns the same on-chain Domain.
Addressing a Domain by name is a different operation. It’s Namegraph forward traversal: domain(by: { name: "vitalik.eth" }) walks from the ENSv2 Root Registry → "eth" in that Registry → the Registry that "eth" points at (the EthRegistry) → "vitalik" in that Registry. The Domain returned is whichever on-chain entity that path currently resolves to.
These two views are not interchangeable:
- The
idyou receive from a name lookup is the stable reference to the on-chain entity that the Namegraph currently resolves"vitalik.eth"to. - But
"vitalik.eth"is not a stable reference to that entity. The Namegraph can be re-parented or re-aliased — and tomorrow,"vitalik.eth"may resolve to an entirely different on-chain Domain.
Rule of thumb: address by name when you’re answering “which Domain would records come from if resolved right now”; address by id when you’re answering “what’s the latest state of this specific on-chain entity?”.
Polymorphism via GraphQL interfaces
Section titled “Polymorphism via GraphQL interfaces”Domain, Registry, and Registration are GraphQL interfaces, with concrete types implementing each:
Domain→ENSv1Domain,ENSv2DomainRegistry→ENSv1Registry,ENSv1VirtualRegistry,ENSv2RegistryRegistration→BaseRegistrarRegistration,NameWrapperRegistration,ThreeDNSRegistration,ENSv2RegistryRegistration,ENSv2RegistryReservation
Shared fields are available unconditionally on the interface. Protocol- or implementation-specific fields are reached via typed inline fragments — ... on ENSv1Domain { rootRegistryOwner }, ... on ENSv2Domain { tokenId }, ... on BaseRegistrarRegistration { wrapped { fuses } }, ... on NameWrapperRegistration { fuses }. The result is a single query that compiles, type-checks, and returns the right fields for whichever concrete type each row turns out to be.
Interpreted Names everywhere
Section titled “Interpreted Names everywhere”Every name and label crossing the Omnigraph surface is an Interpreted Name (or Interpreted Label). Each label in an Interpreted Name is either a normalized literal label or an Encoded LabelHash ([abc123…]) when the literal isn’t known. This eliminates the most common ENS UI footguns — unnormalized labels, unhealed hashes, and rendering surprises — at the schema layer, making UI rendering trivial. See terminology for the full definition.
Relay-spec connections
Section titled “Relay-spec connections”Every list field in the schema is a Relay-spec Connection with edges, pageInfo, and totalCount. Cursor-based pagination is idiomatic in urql, Apollo, Relay, and most modern GraphQL clients — infinite scroll and stable pagination work out of the box, with no per-endpoint plumbing.
A complete audit log of ENS Events
Section titled “A complete audit log of ENS Events”The Omnigraph indexes every onchain Event relevant to ENS and exposes it from the entities each Event relates to:
Domain.events— every Event for a specific DomainResolver.events— every Event emitted by a specific ResolverAccount.events— every Event for which an Account is the HCA-awaresenderPermissions.events,PermissionsUser.events— Permission grant and revocation history
Each Event carries chain, block, transaction, and log metadata, plus an HCA-aware sender field distinct from the raw tx.from for HCA-mediated transactions.
First-class Permissions
Section titled “First-class Permissions”Permissions are modeled as top-level entities. Permissions represents a contract that manages role grants; PermissionsResource is an addressable resource within that contract; PermissionsUser is a specific user’s role bitmap on a specific resource.
Registries, Resolvers, and ENSv2 Domains all expose their Permissions directly (Registry.permissions, Resolver.permissions, ENSv2Domain.permissions), and an Account can be queried for every Permission it’s been granted (Account.permissions, Account.registryPermissions, Account.resolverPermissions). Access-aware UIs — “which Domains can this address manage?”, “who can update this Registry?” — become a single query.
Example
Section titled “Example”The polymorphism, Namegraph traversal, Canonical Name, Events, and version-specific fragments — all in one query:
query EthAndSubdomains { domains(where: { name: { eq: "eth" } }) { edges { node { __typename id canonical { name { interpreted } depth } events(first: 5) { totalCount edges { node { timestamp transactionHash } } } subdomains(first: 10, order: { by: NAME }) { totalCount edges { node { __typename id canonical { name { interpreted } } ... on ENSv1Domain { rootRegistryOwner { address } } ... on ENSv2Domain { tokenId } } } } ... on ENSv1Domain { rootRegistryOwner { address } } ... on ENSv2Domain { tokenId } } } }}