Loading...
;
if (error) return No account found for '{address}'.
;
const { account } = data;
const primaryName = account.resolve?.primaryName;
const addresses = primaryName?.resolve?.profile?.addresses;
return (
Address: {account.address}
Primary name: {primaryName?.name?.beautified ?? "None set"}
Bitcoin address: {addresses?.bitcoin ?? "Not set"}
Description: {primaryName?.resolve?.profile?.description}
);
}
```
[Full enskit Integration Documentation ](/docs/integrate/integration-options/enskit)
[enskit-react-example app ](https://github.com/namehash/ensnode/tree/main/examples/enskit-react-example)Check out our enskit-react-example for a full example app.
[Interactive enskit example ⚡ ](/docs/integrate/integration-options/enskit/example)Edit and run the enskit-react-example app in your browser with a live preview.
### 3. ENS Omnigraph GraphQL API
[Section titled “3. ENS Omnigraph GraphQL API”](#3-ens-omnigraph-graphql-api)
The ENS Omnigraph API is a GraphQL API following the Relay specification, so you get built-in support for efficient infinite pagination and idiomatic access to all of the ENS protocol within a *unified* ENSv1 + ENSv2 datamodel.
Same query: `address -> primary name -> forward profile` — via raw GraphQL with example response below:
omnigraphcurl
[ Run in ENSAdmin ](https://admin.ensnode.io/api/omnigraph?query=query+HelloWorld%28%24address%3A+Address%21%29+%7B%0A++%23+Lookup+an+Account+by+address.%0A++account%28by%3A+%7B+address%3A+%24address+%7D%29+%7B%0A++++resolve+%7B%0A++++++%23+Reverse+resolve+the+ENS+primary+name+of+the+account%0A++++++%23+using+a+convenient+ETHEREUM+alias+for+mainnet.%0A++++++primaryName%28by%3A+%7B+chainName%3A+ETHEREUM+%7D%29+%7B%0A++++++++%23+Get+the+regular+interpreted+variant+of+the+primary+name%0A++++++++%23+and+also+the+special+beautified+variant+that+optimizes+names%0A++++++++%23+containing+special+characters+such+as+emojis+for+proper+display+in+interfaces.%0A++++++++name+%7B+interpreted+beautified+%7D%0A++++++++resolve+%7B%0A++++++++++%23+If+the+account+has+a+primary+name+on+Ethereum+%28mainnet%29%2C%0A++++++++++%23+forward+resolve+the+interpreted+ENS+profile+of+that+name+in+the+same+query%21%0A++++++++++profile+%7B%0A++++++++++++description%0A++++++++++++avatar+%7B+httpUrl+%7D%0A++++++++++++addresses+%7B+ethereum+bitcoin+%7D%0A++++++++++++socials+%7B%0A++++++++++++++twitter+%7B+handle+httpUrl+%7D%0A++++++++++++++github+%7B+handle+httpUrl+%7D%0A++++++++++++%7D%0A++++++++++%7D%0A++++++++%7D%0A++++++%7D%0A++++%7D%0A%0A++++%23+Also+load+the+count+of+ENSv1+and+ENSv2+domains+owned+by+the+account%0A++++%23+to+see+if+they+have+domains+they+should+upgrade+to+ENSv2.%0A++++%23+For+simplicity+this+example+query+doesn%27t+include+additional+logic%0A++++%23+to+filter+out+domains+that+have+expired.%0A++++v1DomainsCount%3A+domains%28where%3A+%7B+version%3A+ENSv1+%7D%29+%7B+totalCount+%7D%0A++++v2DomainsCount%3A+domains%28where%3A+%7B+version%3A+ENSv2+%7D%29+%7B+totalCount+%7D%0A++%7D%0A%7D\&connection=https%3A%2F%2Fapi.alpha.ensnode.io\&variables=%7B%0A++%22address%22%3A+%220xd8da6bf26964af9d7eed9e03e53415d37aa96045%22%0A%7D)Open an interactive playground to execute this example on our [alpha ENSNode instance.](/docs/hosted-instances#ensnode-alpha)
GraphQL
```graphql
query HelloWorld($address: Address!) {
# Lookup an Account by address.
account(by: { address: $address }) {
resolve {
# Reverse resolve the ENS primary name of the account
# using a convenient ETHEREUM alias for mainnet.
primaryName(by: { chainName: ETHEREUM }) {
# Get the regular interpreted variant of the primary name
# and also the special beautified variant that optimizes names
# containing special characters such as emojis for proper display in interfaces.
name { interpreted beautified }
resolve {
# If the account has a primary name on Ethereum (mainnet),
# forward resolve the interpreted ENS profile of that name in the same query!
profile {
description
avatar { httpUrl }
addresses { ethereum bitcoin }
socials {
twitter { handle httpUrl }
github { handle httpUrl }
}
}
}
}
}
# Also load the count of ENSv1 and ENSv2 domains owned by the account
# to see if they have domains they should upgrade to ENSv2.
# For simplicity this example query doesn't include additional logic
# to filter out domains that have expired.
v1DomainsCount: domains(where: { version: ENSv1 }) { totalCount }
v2DomainsCount: domains(where: { version: ENSv2 }) { totalCount }
}
}
```
Variables
```json
{
"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
}
```
Output
```json
{
"data": {
"account": {
"v2DomainsCount": {
"totalCount": 0
},
"v1DomainsCount": {
"totalCount": 514
},
"resolve": {
"primaryName": {
"name": {
"interpreted": "vitalik.eth",
"beautified": "vitalik.eth"
},
"resolve": {
"profile": {
"description": "mi pinxe lo crino tcati",
"avatar": {
"httpUrl": "https://euc.li/vitalik.eth"
},
"addresses": {
"ethereum": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"bitcoin": null
},
"socials": {
"twitter": {
"handle": "VitalikButerin",
"httpUrl": "https://x.com/VitalikButerin"
},
"github": {
"handle": "vbuterin",
"httpUrl": "https://github.com/vbuterin"
}
}
}
}
}
}
}
}
}
```
Output matches a point in time snapshot GraphQL response from our [alpha ENSNode instance](/docs/hosted-instances#ensnode-alpha). Live output depends on the configuration of your ENSNode instance and ENS state updates.
cURL
```bash
# POST JSON to your ENSNode Omnigraph endpoint (same path enssdk uses).
curl -sS -X POST "https://api.alpha.ensnode.io/api/omnigraph" \
-H "Content-Type: application/json" \
-d '{
"query": "query HelloWorld($address: Address!) { account(by: { address: $address }) { resolve { primaryName(by: { chainName: ETHEREUM }) { name { interpreted beautified } resolve { profile { description avatar { httpUrl } addresses { ethereum bitcoin } socials { twitter { handle httpUrl } github { handle httpUrl } } } } } } v1DomainsCount: domains(where: { version: ENSv1 }) { totalCount } v2DomainsCount: domains(where: { version: ENSv2 }) { totalCount } } }",
"variables": {"address":"0xd8da6bf26964af9d7eed9e03e53415d37aa96045"}
}'
```
Response
```json
{
"data": {
"account": {
"v2DomainsCount": {
"totalCount": 0
},
"v1DomainsCount": {
"totalCount": 514
},
"resolve": {
"primaryName": {
"name": {
"interpreted": "vitalik.eth",
"beautified": "vitalik.eth"
},
"resolve": {
"profile": {
"description": "mi pinxe lo crino tcati",
"avatar": {
"httpUrl": "https://euc.li/vitalik.eth"
},
"addresses": {
"ethereum": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"bitcoin": null
},
"socials": {
"twitter": {
"handle": "VitalikButerin",
"httpUrl": "https://x.com/VitalikButerin"
},
"github": {
"handle": "vbuterin",
"httpUrl": "https://github.com/vbuterin"
}
}
}
}
}
}
}
}
}
```
Output matches a point in time snapshot GraphQL response from our [alpha ENSNode instance](/docs/hosted-instances#ensnode-alpha). Live output depends on the configuration of your ENSNode instance and ENS state updates.
[Full ENS Omnigraph GraphQL API Documentation ](/docs/integrate/integration-options/omnigraph-graphql-api)
[omnigraph-graphql-example app ](https://github.com/namehash/ensnode/tree/main/examples/omnigraph-graphql-example)Check out our omnigraph-graphql-example for a full example app.
### 4. Further Integration Options
[Section titled “4. Further Integration Options”](#4-further-integration-options)
Beyond [`enssdk`](/docs/integrate/integration-options/enssdk), [`enskit`](/docs/integrate/integration-options/enskit), and the [Omnigraph GraphQL API](/docs/integrate/integration-options/omnigraph-graphql-api), ENSNode exposes a deeper set of integration surfaces for advanced use cases:
* **[ENSDb (SQL)](/docs/integrate/integration-options/ensdb)** — query the indexed ENSv1 and ENSv2 datasets directly via SQL for custom analytics or your own service layer, from any language with a Postgres driver.
* **[ENSDb Writers (Indexers)](/docs/integrate/integration-options/ensdb-writers)** — enable all other layers of the ENSNode stack to build on your custom indexing engine.
* **[ENSDb Readers (Custom APIs)](/docs/integrate/integration-options/ensdb-readers)** — build your own custom APIs and services on top of ENSDb using any programming language or framework.
* **[ENSNode Plugins (Indexed Data Models)](/docs/integrate/integration-options/ensnode-plugins)** — define how onchain data should be indexed into ENSDb.
* **[enscli (CLI)](/docs/integrate/integration-options/enscli)** — resolve names, look up records, and run ad-hoc Omnigraph queries from the terminal — built for humans and AI agents alike.
* **[ensskills (AI agents)](/docs/integrate/integration-options/ensskills)** — a curated set of skills that gives AI coding agents a well-defined contract for working with ENS.
* **[ensdb-cli (ENSDb Snapshots)](/docs/integrate/integration-options/ensdb-cli)** — bootstrap a fresh ENSDb in minutes from portable, versioned snapshots instead of waiting days on a full historical backfill.
* **[ENSEngine (Live push notifications)](/docs/integrate/integration-options/ensengine)** — subscribe to ENS-aware live notifications driven by changes in ENSDb, so your apps can stop polling and start reacting.
[See all Integration Options ](/docs/integrate/integration-options)Includes more advanced integration options not introduced in this quickstart guide
# AI / LLM Tooling
> AI and LLM tooling for building on ENSv2.
We’re building the infrastructure to make ENS a first-class citizen for AI agents.
The foundation for how developers and their AI agents reach for ENS is [`ensskills`](/docs/integrate/integration-options/ensskills), that teach your AI assistant about ENS, ENSNode, the ENS Omnigraph, and how to drive [`enscli`](/docs/integrate/integration-options/enscli) — an agent- and human-friendly CLI — on your behalf.
Version compatibility with hosted instances
[Our hosted ENSNode instances](/docs/hosted-instances) currently run ENSNode `1.15.2`. `ensskills` is version-locked to the ENSNode suite—the Omnigraph schema and example queries the skills teach are bundled at a specific version—so pin `ensskills@1.15.2` and the matching `enscli@1.15.2` to keep your agent's ENS knowledge and queries matched to the deployed API.
## Quickstart (`npm`/`pnpm`/`yarn`/`bun`)
[Section titled “Quickstart (npm/pnpm/yarn/bun)”](#quickstart-npmpnpmyarnbun)
Add `ensskills` and [`skills-npm`](https://github.com/antfu/skills-npm) to your project and wire a `prepare` script so the pinned skills re-sync into your agent directories (`.claude/skills`, `.cursor/skills`, …) on every install:
package.json
```jsonc
{
"devDependencies": {
"ensskills": "1.15.2",
"skills-npm": "^1"
},
"scripts": {
"prepare": "skills-npm"
}
}
```
```bash
npm install # symlinks the skills for your detected agents
```
## Quickstart (`npx skills`)
[Section titled “Quickstart (npx skills)”](#quickstart-npx-skills)
Not in a Node project? [`skills`](https://github.com/vercel-labs/skills) installs every ENS skill straight from the repo, pinned to the matching `v…` release tag:
```bash
npx skills add https://github.com/namehash/ensnode/tree/v1.15.2/packages/ensskills/skills --skill '*'
```
## Next Steps
[Section titled “Next Steps”](#next-steps)
That’s it — your AI agent now has all of [`ensskills`](/docs/integrate/integration-options/ensskills) at its disposal.
prompt.md
```md
Which address currently owns vitalik.eth
and how many other domains do they own?
```
[enscli ](/docs/integrate/integration-options/enscli)An agent- and human-friendly CLI for the ENS Omnigraph API.
[ensskills ](/docs/integrate/integration-options/ensskills)Skill bundles that give AI agents an opinionated contract for ENS.
## Documentation as `llms.txt`
[Section titled “Documentation as llms.txt”](#documentation-as-llmstxt)
If you aren’t using `ensskills`, the entire documentation site is also published in the [`llms.txt`](https://llmstxt.org/) format so any agent or LLM can load it directly as context:
* [`/llms.txt`](https://ensnode.io/llms.txt) — a structured index of the documentation with links to every page.
* [`/llms-full.txt`](https://ensnode.io/llms-full.txt) — the entire documentation concatenated into a single file, ready to drop into a model’s context window.
Paste this at the top of a prompt to point your agent at the full documentation before asking your question:
prompt.md
```md
Load the ENSNode documentation from https://ensnode.io/llms-full.txt to answer the following question:
```
# ENS Subgraph
> The ENS Subgraph quietly became critical infrastructure for ENS and the broader web3 ecosystem — and it cannot carry that ecosystem into ENSv2. Here's who depends on it, and what comes next.
🚨 The ENS Subgraph is not ENSv2 compatible
The ENS Subgraph **fundamentally fails as a source of ENS data as soon as ENSv2 launches.**
[Keep ENS apps working 🚨 ](/docs/integrate/why-ensnode/keep-ens-working)See apps currently set to break when ENSv2 launches unless they upgrade to the new ENS Omnigraph API.
[Key Limitations 🚨 ](/docs/integrate/ens-subgraph/key-limitations)See the full list of Key Subgraph Limitations and how the Omnigraph addresses them.
Start here instead: the ENS Omnigraph API
The [ENS Omnigraph API](/docs/integrate/omnigraph) is the ENSv2-ready replacement: one unified, typed GraphQL API over **both ENSv1 and ENSv2**, multichain by default, with protocol-correct [ENS Protocol Acceleration](/docs/integrate/omnigraph/protocol-acceleration) resolution built in.
[ENS Omnigraph API ](/docs/integrate/omnigraph)ENSv2-ready unified GraphQL API over both ENSv1 and ENSv2 — start here for new integrations.
[ENS Unigraph SQL ](/docs/integrate/unigraph)Direct SQL access to the unified, multichain ENS data model.
## ENSNode Subgraph compatibility
[Section titled “ENSNode Subgraph compatibility”](#ensnode-subgraph-compatibility)
ENSNode maintains a verified Subgraph-compatible API for migrating existing integrations from The Graph, but it is **not** the path forward for ENSv2.
[ENSNode’s Backwards Compatibility with the ENS Subgraph ](/docs/integrate/ens-subgraph/backwards-compatibility#ensnodes-backwards-compatibility-with-the-ens-subgraph)A verified Subgraph-compatible GraphQL endpoint for migrating existing integrations to ENSNode.
# Backwards Compatibility
> How ENSNode provides a verified Subgraph-compatible API for migrating existing integrations, the ecosystem it grew out of, and how to query it correctly.
This page is background on the ENS Subgraph and the ecosystem ENSNode grew out of. ENSNode maintains a verified Subgraph-compatible API for migrating existing integrations, but it is **not** the path forward for ENSv2 — see [Key Limitations](/docs/integrate/ens-subgraph/key-limitations) and the [ENS Omnigraph API](/docs/integrate/omnigraph) for what is.
## The Graph & Graph Node
[Section titled “The Graph & Graph Node”](#the-graph--graph-node)
[The Graph](https://thegraph.com/) leads development of [Graph Node](https://thegraph.com/docs/en/indexing/tooling/graph-node/), an [open source software application](https://github.com/graphprotocol/graph-node) for indexing blockchain data.
## Subgraphs
[Section titled “Subgraphs”](#subgraphs)
Each Graph Node server can run any number of “subgraphs”. Each subgraph is essentially a plugin describing:
1. A strategy for how the Graph Node should index blockchain data.
2. A schema for a GraphQL API providing access to the indexed data.
## ENS Subgraph
[Section titled “ENS Subgraph”](#ens-subgraph)
[ENS Labs](https://www.enslabs.org/) has led development of the [ENS Subgraph](https://github.com/ensdomains/ens-subgraph). In the past, this was the “official” strategy for indexing ENS data. Additional background info is available in [official ENS docs](https://docs.ens.domains/web/subgraph).
## Graph Network
[Section titled “Graph Network”](#graph-network)
Operating your own Graph Node server instance can be complex, expensive, and time consuming. An alternative is to use The Graph’s semi-decentralized network of indexers operating Graph Node instances.
This network provides access to a [semi-decentralized ENS Subgraph](https://thegraph.com/explorer/subgraphs/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH?view=Query\&chain=arbitrum-one). Developers are welcome to use this rate limited API endpoint above for testing, but are highly encouraged to sign up for an account with The Graph to get their own (paid) API key.
## ENSNode’s Backwards Compatibility with the ENS Subgraph
[Section titled “ENSNode’s Backwards Compatibility with the ENS Subgraph”](#ensnodes-backwards-compatibility-with-the-ens-subgraph)
To support the ENS ecosystem’s transition away from legacy ENS indexing strategies to ENSNode, ENSNode provides a verified backwards compatible ENS Subgraph GraphQL endpoint. This therefore also provides backwards compatibility with `ensjs`.
1. For those that wish to host their own ENS indexer, it is faster and easier to deploy ENSNode than to run an ENS Subgraph instance.
2. For those building an app that simply want to query the legacy ENS Subgraph API in the easiest way possible, we make this freely available through [our hosted ENSNode instances](/docs/hosted-instances).
## Self-hosted ENSNode instance configuration for ENS Subgraph compatibility
[Section titled “Self-hosted ENSNode instance configuration for ENS Subgraph compatibility”](#self-hosted-ensnode-instance-configuration-for-ens-subgraph-compatibility)
To enable full ENS Subgraph compatibility on a self-hosted ENSNode instance, configure ENSIndexer with `SUBGRAPH_COMPAT=true`. This single flag:
1. **Applies Subgraph Indexing Behavior**: Uses Subgraph Interpreted Labels and Names, allowing unnormalized labels to be returned as they appear in the original ENS Subgraph
2. **Sets Default Plugins & Label Set**: Defaults to `PLUGINS=subgraph`, `LABEL_SET_ID=subgraph` and `LABEL_SET_VERSION=0` to match subgraph indexing logic and label healing behavior
When `SUBGRAPH_COMPAT=false` (default), ENSIndexer operates in enhanced mode with:
* **Enhanced Indexing Behavior**: Uses Interpreted Labels and Names with improved security by encoding unnormalized labels as labelhashes
* **Expanded Plugin Support**: Defaults to `PLUGINS=subgraph,basenames,lineanames,threedns,protocol-acceleration,registrars,tokenscope` for multichain ENS indexing
* **Reverse Address Healing**: Attempts to heal subnames of addr.reverse for enhanced reverse resolution support
## Compatibility Tooling
[Section titled “Compatibility Tooling”](#compatibility-tooling)
ENSNode has developed tooling to verify subgraph compatibility and ease migration from the ENS Subgraph. The tools in the [ens-subgraph-transition-tools](https://github.com/namehash/ens-subgraph-transition-tools) repository help users verify ENSNode’s subgraph-compatibility.
* `snapshot-eq` — verify subgraph-equivalent data via snapshots at specific blockheights
See the [ens-subgraph-transition-tools](https://github.com/namehash/ens-subgraph-transition-tools) README for additional context and usage instructions.
[ens-subgraph-transition-tools ](https://github.com/namehash/ens-subgraph-transition-tools)Tools for verifying ENSNode's subgraph compatibility
## Querying the Subgraph-Compatible API correctly
[Section titled “Querying the Subgraph-Compatible API correctly”](#querying-the-subgraph-compatible-api-correctly)
The care required to query the ENS Subgraph correctly is itself one of its [Key Limitations](/docs/integrate/ens-subgraph/key-limitations) — the guidance below exists because the Subgraph data model exposes raw protocol internals that every client has to handle carefully. If you are migrating an existing integration onto ENSNode’s Subgraph-compatible API, the following patterns apply.
Terminology
It may be helpful to refer to the [Terminology](/docs/reference/terminology) guide when reading this section.
### Use the node as the stable identifier
[Section titled “Use the node as the stable identifier”](#use-the-node-as-the-stable-identifier)
When querying for specific names or sets of names, it’s crucial to understand that the representation of labels (both known and unknown) should not generally be assumed to be immutable identifiers. Here’s why:
**Label Mutability**
* ENSNode indexes all onchain events where a subname is created in the ENS Registry. When these events are indexed, the labelhash of the subname is always known, however sometimes the label of the subname is unknown (strictly from indexed onchain data). When this happens ENSNode attempts to look up the label for the labelhash through an attached ENSRainbow server. If this lookup succeeds, ENSNode will represent the subname using its true label. If this lookup fails, some label to represent the subname is still required. Therefore, ENSNode will represent the “unknown label” using its labelhash in the format `[labelhash]`.
* Changes in the set of healable labels maintained by an ENSRainbow instance can modify the resulting indexed state in attached ENSNode instances. For example, if at “time 1” ENSRainbow does not have knowledge to heal label X, but at “time 2” it does (from the perspective of an ENSNode client) a label represented as “unknown” at “time 1” could transition to become known at “time 2”. Each ENSNode instance should ensure it is attached to an ENSRainbow instance that only grows its set of healable labels across time, such that from the perspective of an ENSNode client a “known label” should never transition back to its “unknown” representation. However, if an ENSNode instance is improperly operated, such a situation could occur.
**ENS Normalization Standard Changes**
The [ENSIP-15: ENS Name Normalization Standard](https://docs.ens.domains/ensip/15) may change across time such that the set of normalizable names grows (thankfully it should never shrink). For example, consider a new Unicode release that standardizes new emoji. The ENS Normalize standard may subsequently change to expand support for those new emoji.
Therefore, always use the node of a name (calculated by the namehash of the name) as the stable identifier when querying. The node of a name is immutable across time and works for all names, even if they are unknown, unnormalized, or subgraph-unindexable.
#### Pattern 1: Names from User Input / Offchain Data
[Section titled “Pattern 1: Names from User Input / Offchain Data”](#pattern-1-names-from-user-input--offchain-data)
When querying for names that originate from user input (e.g., search fields, user-entered addresses) or offchain data (e.g. traditional data sources), always apply the following procedure within your app:
1. Normalize the name according to ENSIP-15.
2. Calculate the `node` for the normalized name using the `namehash` function.
3. Query the `id` field of domains using the `node` calculated in the previous step, rather than the name itself (for backwards compatibility with the ENS Subgraph, the field for the `node` of the name is actually the `id` field).
Example:
First, let’s prepare the name for querying by normalizing it and calculating its node:
prep-example.ts
```typescript
import { namehashInterpretedName, normalizeName, asInterpretedName } from "enssdk";
// 1. Normalize the user input according to ENSIP-15
const userInput = "Vitalik.eth";
const normalizedName = normalizeName(userInput);
// 2. Calculate the node from the normalized name
const node = namehashInterpretedName(asInterpretedName(normalizedName));
```
Now use this node to query the domain id:
query.graphql
```graphql
{
domain(id: "0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835") {
id
name
labelName
labelhash
createdAt
}
}
```
The query will return the domain information:
response.json
```json
{
"data": {
"domain": {
"createdAt": "1497775154",
"id": "0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835",
"labelName": "vitalik",
"labelhash": "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc",
"name": "vitalik.eth"
}
}
}
```
#### Pattern 2: Names from Onchain Data
[Section titled “Pattern 2: Names from Onchain Data”](#pattern-2-names-from-onchain-data)
When querying for name values sourced directly from onchain data (e.g., ENS NFTs, contract events), you must:
1. Skip any normalization step — the name value passed to namehash must be exactly as it appears onchain, even if unnormalized.
2. Calculate the node by taking the namehash of the onchain name (without any normalization). Be warned however that unnormalized labels may contain ”.” characters within the label value which can confuse namehash if special precautions are not taken.
3. Query the domain id using the node of the name.
This pattern is crucial when dealing with unnormalized names that exist onchain. For example, if while examining onchain data you see a registration for “EXAMPLE.eth” (note the uppercase unnormalized characters), attempting to normalize this name in the process of querying for additional information about it would result in looking up details for a different node in the ENS Registry (in this case the node for “example.eth” rather than “EXAMPLE.eth”).
The query structure in Pattern 2 remains the same as Pattern 1, except the normalization step is skipped to ensure the node that you query data about is the intended node.
### Never normalize labels returned by ENSNode
[Section titled “Never normalize labels returned by ENSNode”](#never-normalize-labels-returned-by-ensnode)
Configuration Note
ENSNode’s handling of unnormalized labels is controlled by the `SUBGRAPH_COMPAT` configuration option:
* `SUBGRAPH_COMPAT=true` allows unnormalized labels to be returned as Subgraph Interpreted Labels (required for full ENS Subgraph compatibility)
* `SUBGRAPH_COMPAT=false` (default) encodes unnormalized labels as Interpreted Labels, improving security
When `SUBGRAPH_COMPAT=true`, ENSNode may return unnormalized labels as [Subgraph Interpreted Labels](/docs/reference/terminology#subgraph-interpreted-label) associated with indexed names. **ENSNode clients should never attempt to normalize labels returned by ENSNode.** This is because when ENSNode returns an unnormalized label, that label is associated with a specific node that has been indexed. Normalizing an unnormalized label in this context would represent a different node.
An ENSNode client is permitted to validate that all labels returned by ENSNode are in normalized form, and to reject any names with unnormalized labels from further processing. However, the key principle is that an ENSNode client should never normalize returned labels, as normalization transforms the label and therefore also the node associated with the name the label is contained within.
When `SUBGRAPH_COMPAT=false` (default), ENSNode uses [Interpreted Labels](/docs/reference/terminology#interpreted-label) instead of [Subgraph Interpreted Labels](/docs/reference/terminology#subgraph-interpreted-label), which helps avoid edge cases related to null bytes, full-stop characters (periods), or exotic unicode characters. When names are returned from any of the ENSNode APIs, including the Subgraph-compatible GraphQL API, names will be [Interpreted Names](/docs/reference/terminology#interpreted-name).
### Calculating the node for names that contain Encoded LabelHashes
[Section titled “Calculating the node for names that contain Encoded LabelHashes”](#calculating-the-node-for-names-that-contain-encoded-labelhashes)
According to [ENSIP-1](https://docs.ens.domains/ensip/1#namehash-algorithm), the namehash algorithm makes no special consideration for Encoded LabelHashes, and therefore interprets Encoded-LabelHash-looking strings as Literal Label values. Due to this behavior, we recommend using an “Encoded-LabelHash-aware” namehash algorithm implementation such as the [viem namehash implementation](https://github.com/wevm/viem/blob/fe558fdef7e2e9cd5f3f57d8bdeae0c7ff67a1b0/src/utils/ens/namehash.ts#L36-L51).
SUBGRAPH\_COMPAT
The following is relevant when `SUBGRAPH_COMPAT=false` (default) and ENSNode is using Interpreted Labels for handling unknown labels.
When an **Unknown** or `subgraph-unindexable` label is encountered, ENSNode represents it as an **Encoded LabelHash** in the format `[{labelhash}]`, where `{labelhash}` is the labelhash of the label in question. This representation creates an interesting edge case that must be handled carefully:
Consider an unnormalized label that literally looks like `[24695ee963d29f0f52edfdea1e830d2fcfc9052d5ba70b194bddd0afbbc89765]`. Because this label contains square brackets (`subgraph-unindexable` characters), it will be represented as the unknown label: `[80968d00b78a91f47b233eaa213576293d16dadcbbdceb257bca94b08451ba7f]`
Therefore, this represents the `subgraph-unindexable` label as an **Encoded LabelHash**, encoding the labelhash of the original unnormalized label (including its square brackets) in square brackets. This demonstrates why square brackets are considered `subgraph-unindexable` — they create ambiguity between literal labels and the representation of **Encoded LabelHashes**.
When ENSNode encounters a `subgraph-unindexable` label, it will represent it as an **Encoded LabelHash** even if the actual label data is available.
For more detailed information about `subgraph-unindexable` labels and their handling, please refer to the [ENSNode SDK implementation](https://github.com/namehash/ensnode/blob/main/apps/ensindexer/src/lib/is-label-subgraph-indexable.ts).
## Unplanned Features
[Section titled “Unplanned Features”](#unplanned-features)
The following features of the subgraph GraphQL API are explicitly unsupported and are not planned.
* [1-level-nested Entity `_orderBy` param](https://thegraph.com/docs/en/subgraphs/querying/graphql-api#nested-entity-sorting-example)
* [time travel queries](https://thegraph.com/docs/en/subgraphs/querying/graphql-api#time-travel-queries-example)
* [\_change\_block filtering](https://thegraph.com/docs/en/subgraphs/querying/graphql-api#block-based-filtering-example)
* [fulltext search queries](https://thegraph.com/docs/en/subgraphs/querying/graphql-api#full-text-search-example)
# ENS Subgraph Examples
> Examples of integrating ENSNode's Subgraph-compatible API with popular ENS libraries.
Examples of integrating ENSNode’s Subgraph-compatible GraphQL API with popular ENS libraries.
[With ENSjs ](/docs/integrate/ens-subgraph/examples/with-ensjs)Point @ensdomains/ensjs at an ENSNode Subgraph-compatible endpoint.
[With Viem ](/docs/integrate/ens-subgraph/examples/with-viem)Configure a viem Chain's subgraph URL to use ENSNode.
# Using ENSNode with ENSjs
To use ENSNode with `@ensdomains/ensjs`, follow the [ENSjs documentation for custom subgraph URIs](https://github.com/ensdomains/ensjs/blob/17ab314/docs/basics/custom-subgraph-uris.md), replacing the subgraph URI with your ENSNode’s subgraph-compatible api endpoint.
No backend required
You don't need to run your own ENSNode to follow this guide — the steps below default to a NameHash-hosted instance. Browse the available deployments below.
Version compatibility with hosted instances
[Our hosted ENSNode instances](/docs/hosted-instances) currently run ENSNode `1.15.2`. The Omnigraph GraphQL schema is bundled inside the SDK and consumed by the `gql.tada` TypeScript plugin to type your queries, so pin an **exact** version (no `^` or `~`) of `enssdk@1.15.2` (and `enskit@1.15.2` when using React) to keep your generated types matched to the deployed schema. Use these exact install commands:
```
npm install enssdk@1.15.2
# or, for React apps:
npm install enskit@1.15.2 enssdk@1.15.2
```
[Hosted ENSNode Instances ](/docs/hosted-instances)
example.ts
```ts
import { http, createClient } from "viem";
import { mainnet } from "viem/chains";
import { addEnsContracts } from "@ensdomains/ensjs";
import { getNamesForAddress } from "@ensdomains/ensjs/subgraph";
const mainnetWithEns = addEnsContracts(mainnet);
const chain = {
...mainnetWithEns,
subgraphs: {
ens: {
// use the NameHash-hosted 'alpha' instance subgraph-compatible responses with (mainnet, Base, and Linea) names
url: "https://api.alpha.ensnode.io/subgraph",
// or use your own local instance
// url: 'http://localhost:42069/subgraph',
},
},
};
const client = createClient({
chain,
transport: http(),
});
const names = await getNamesForAddress(client, {
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth
});
```
## Well-Known Subgraph Queries
[Section titled “Well-Known Subgraph Queries”](#well-known-subgraph-queries)
Once ENSjs is pointed at an ENSNode Subgraph-compatible endpoint, its Subgraph functions work unchanged. ENSNode’s Subgraph-compatible GraphQL API provides full compatibility with these use cases (and all other possible queries, with the only exception of the [unplanned features](/docs/integrate/ens-subgraph/backwards-compatibility#unplanned-features)). The functions below are the patterns we see most often in the wild.
Contributions
If you’d like to highlight additional query patterns of the ENS Subgraph GraphQL, please [contribute to this documentation](https://github.com/namehash/ensnode/issues).
### ENSjs Subgraph functions
[Section titled “ENSjs Subgraph functions”](#ensjs-subgraph-functions)
* [`getDecodedName`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getDecodedName.ts) — gets the full name for a name with unknown labels from the subgraph (heals encoded labels, splits the name into labels, finds domains by id, and queries the domain by namehash).
* [`getNameHistory`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getNameHistory.ts) — retrieves all events associated with a name.
* [`getNamesForAddress`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getNamesForAddress.ts) — gets all names related to an address via registrant, owner, wrappedOwner, and resolvedAddress; supports `searchString`, filtering (by expiry, reverse records, empty domains), ordering (by expiry date, name, labelName, createdAt), and pagination.
* [`getSubgraphRecords`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getSubgraphRecords.ts) — gets the records for a name from the subgraph; allows querying by a specific resolver id.
* [`getSubgraphRegistrant`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getSubgraphRegistrant.ts) — gets the name registrant from the subgraph (`.eth` second-level domains only).
* [`getSubnames`](https://github.com/ensdomains/ensjs/blob/17ab314/packages/ensjs/src/functions/subgraph/getSubnames.ts) — gets the subnames for a name; supports `searchString`, filtering (by expiry, empty domains), ordering, and pagination.
### ENSv1 Manager App queries
[Section titled “ENSv1 Manager App queries”](#ensv1-manager-app-queries)
These query patterns come from the ENSv1 Manager App (`ens-app-v3`). They may not go through ENSjs directly, but they’re useful references for the kinds of Subgraph queries real apps depend on:
* [`useResolverExists`](https://github.com/ensdomains/ens-app-v3/blob/328692ae832618f8143916c143b7e4cb9e520811/src/hooks/useResolverExists.ts#L27) — checks if a resolver exists.
* [`useRegistrationData`](https://github.com/ensdomains/ens-app-v3/blob/328692ae832618f8143916c143b7e4cb9e520811/src/hooks/useRegistrationData.ts#L31) — gets registration by id and `nameRegistered` events.
## ENSjs Documentation
[Section titled “ENSjs Documentation”](#ensjs-documentation)
Refer to the ENSjs documentation for further usage.
[ENSjs Documentation ](https://github.com/ensdomains/ensjs/)
# Using ENSNode with `viem/chain`
Some libraries (for example, [`ENSjs`](/docs/integrate/subgraph/examples/with-ensjs)) use a `viem/chain` object to identify the ENS Subgraph url. If you’re integrating with a library that expects a url in the `subgraph` key for your chain, you can update the `Chain` spec to use ENSNode like so:
example.ts
```ts
import { mainnet } from "viem/chains";
const mainnetWithENSNode = {
...mainnet,
subgraphs: { ens: { url: "https://api.alpha.ensnode.io/subgraph" } },
};
```
# Key Limitations
> The ENS Subgraph was never designed to be a complete view of ENS. These are the limitations that break apps today — and that get worse the moment ENSv2 launches.
The ENS Subgraph was never designed to be a complete view of ENS. It indexes a single chain’s events and exposes them largely as-is — leaving every app that builds on it to work around a long list of gaps. Many apps don’t work around them correctly, and the result is shipping real bugs in some of the most-used software in the ecosystem.
Each of the following limitations is a place where the burden of getting ENS right is pushed onto app developers.
There is a path forward
The [ENS Omnigraph API](/docs/integrate/omnigraph) is built to close every gap on this page: one unified, typed API over ENSv1 and ENSv2, multichain by default, with [ENS Protocol Acceleration](/docs/integrate/omnigraph/protocol-acceleration) for resolution.
## Two systems, neither complete
[Section titled “Two systems, neither complete”](#two-systems-neither-complete)
DIY ENS Integrations are Hard
Historically, full access to ENS data required two separate data-fetching strategies working in parallel:
1. **ENS resolution** — RPC calls with CCIP-Read support for offchain data (e.g. via `viem` or `wagmi`) to perform forward or reverse resolution.
2. **Indexed ENS data** — the ENS Subgraph, for discovering names owned by an address and all other ENS state outside of resolution.
Neither system alone is complete. Resolution gives you resolver records but no access to the rest of ENS state, and it is painfully “close to the metal.” The Subgraph gives you queryable indexed data but cannot resolve names and carries the limitations below. Apps have had to live with the split, its limitations, and its downstream complexity — and **with ENSv2, the complexity of ENS’s onchain state meaningfully increases.**
One unified API
The [Omnigraph API](/docs/integrate/omnigraph) bundles access to ENS onchain resources (like Domains and Registrations) with Protocol Accelerated Resolution; a single unified API for all of your ENS needs.
## No ENS resolution — and apps that fake it are broken
[Section titled “No ENS resolution — and apps that fake it are broken”](#no-ens-resolution--and-apps-that-fake-it-are-broken)
Faking resolution ships real bugs
The Subgraph does not perform ENS resolution. It has no concept of the ENS Universal Resolver, CCIP-Read, or ENSIP-10 wildcard resolution. Despite this, developers routinely reach for the Subgraph to resolve names — because it’s the indexed data source already in front of them — which produces incorrect results because it **doesn’t follow the ENS Forward Resolution protocol**.
This isn’t hypothetical. It happens in widely-used software:
* **[Stamp](https://github.com/snapshot-labs/stamp) by [Snapshot Labs](https://snapshot.box/)** powers the avatars across [Snapshot](https://snapshot.box/), among the most-used DAO infrastructure in the ecosystem. It resolves addresses [directly against the Subgraph](https://github.com/snapshot-labs/stamp/blob/a6d341a65159a1e76d1dc889156c4676e54eea14/src/addressResolvers/ens.ts#L87-L94).
* **[ethVM](https://www.ethvm.com/) by [MyEtherWallet](https://www.myetherwallet.com/)** resolves names [via a generated Subgraph query](https://github.com/EthVM/EthVM/blob/2ad42adc544074ed8cd6c2cba6a7fa0ff4ffc48b/v2/src/core/composables/ResolveName/ensResolveName.generated.ts#L21-L30).
* **[Ethereum Comments Protocol](https://github.com/ecp-eth/comments-monorepo)** resolves names [against an indexer query](https://github.com/ecp-eth/comments-monorepo/blob/c301d76fa56b6b807f135c98273ac9eb5ddebe95/apps/indexer/src/services/resolvers/ens-by-query-resolver.ts) rather than the resolution protocol.
Each of these approaches produces results that diverge from what the ENS protocol *actually* says.
Protocol-correct resolution
The [Omnigraph API](/docs/integrate/omnigraph) performs protocol-correct resolution for you — including the CCIP-Read offchain lookups — so the correct result is the default.
## It forces you to stitch together multiple APIs
[Section titled “It forces you to stitch together multiple APIs”](#it-forces-you-to-stitch-together-multiple-apis)
You become the integration glue
Because the Subgraph can’t resolve names, any app that needs both indexed data *and* resolution has to run two integrations side by side: the Subgraph for indexed state, and a resolution library for records. You reconcile their differences, their failure modes, and their data models yourself. Developers shouldn’t have to care about these implementation details of the ENS protocol — getting “all the ENS data I need,” whether ENSv1, ENSv2, indexed, or resolved, should come from a single unified API.
Write your query once
The [Omnigraph API](/docs/integrate/omnigraph) provides a unified datamodel across ENSv1 and ENSv2: write your query once and your platform automatically understands both protocol versions without any extra work on your end.
## ENSv1 only — blind to ENSv2
[Section titled “ENSv1 only — blind to ENSv2”](#ensv1-only--blind-to-ensv2)
Stale the moment ENSv2 launches
The Subgraph’s data model has no concept of ENSv2. The moment ENSv2 launches (Summer 2026), apps still reading the Subgraph are looking at a stale, partial view of ENS — missing the new ENSv2 Namegraphs entirely. There is no upgrade path: the schema was never designed for it.
The Omnigraph transparently upgrades to ENSv2
The [Omnigraph API](/docs/integrate/omnigraph) provides a unified datamodel across ENSv1 and ENSv2: your app works before, during, and after the ENSv2 release without any changes.
## Single-chain only — misses most names
[Section titled “Single-chain only — misses most names”](#single-chain-only--misses-most-names)
Most ENS names are invisible
The Subgraph indexes a single chain, so it never sees Basenames (`.base.eth`), Lineanames (`.linea.eth`), or 3DNS names (`.box`). A large and growing majority of ENS names already live off of mainnet and are simply invisible to it.
Every chain in one schema
The [Omnigraph API](/docs/integrate/omnigraph) indexes the full suite of onchain ENS names, including Basenames (`.base.eth`), Lineanames (`.linea.eth`) and 3DNS names (`.box`).
## No multichain primary names (ENSIP-19)
[Section titled “No multichain primary names (ENSIP-19)”](#no-multichain-primary-names-ensip-19)
No cross-chain primary names
Beyond being single-chain, the Subgraph has no concept of [ENSIP-19](https://docs.ens.domains/ensip/19) multichain primary names. Even an app willing to query several per-chain Subgraphs cannot reconstruct a name’s primary-name configuration across chains from Subgraph data.
Full ENSIP-19 support
The [Omnigraph API](/docs/integrate/omnigraph) fully implements [ENSIP-19](https://docs.ens.domains/ensip/19) and accurately returns an account’s multichain primary names in milliseconds.
## No concept of the effective resolver (ENSIP-10)
[Section titled “No concept of the effective resolver (ENSIP-10)”](#no-concept-of-the-effective-resolver-ensip-10)
Wrong resolver, wrong records
The Subgraph records the resolver *assigned* to a domain but has no understanding of [ENSIP-10](https://docs.ens.domains/ensip/10) wildcard resolution, and therefore no concept of the *effective* resolver — the resolver that actually answers for a name via a parent’s wildcard resolver. Apps that read the assigned resolver from the Subgraph and assume it’s the effective one get the wrong answer for any name that relies on wildcard resolution.
Assigned and effective resolvers
The [Omnigraph API](/docs/integrate/omnigraph) supports both a Domain’s *assigned* resolver as well as its [ENSIP-10](https://docs.ens.domains/ensip/10) *effective* resolver, so you can write applications that understand the difference, whether you’re resolving up-to-date records or letting users edit records onchain.
## Unnormalized names (ENSIP-15 not applied)
[Section titled “Unnormalized names (ENSIP-15 not applied)”](#unnormalized-names-ensip-15-not-applied)
Normalization is left to you
The Subgraph does not apply [ENSIP-15](https://docs.ens.domains/ensip/15) name normalization. It returns unnormalized labels and names, putting the burden on every consuming app to implement normalization correctly and consistently — and the ones that don’t display or match names incorrectly. ENSNode [replaces unnormalized labels](/docs/integrate/ens-subgraph/backwards-compatibility#never-normalize-labels-returned-by-ensnode) for you, so you automatically enjoy safer handling.
Interpreted Names by default
The [Omnigraph API](/docs/integrate/omnigraph) stores and operates over [Interpreted Names](/docs/reference/terminology#interpreted-name), a consistent name format that ensures that names are composed of labels that are either normalized or [Encoded LabelHashes](/docs/reference/terminology#encoded-labelhash). This means consistent handling and fewer application bugs.
## Unstable domain identification
[Section titled “Unstable domain identification”](#unstable-domain-identification)
Identifiers shift underneath you
Labels in the Subgraph are not stable identifiers. A label that is “unknown” today can become “known” later (as label-healing coverage grows), and the set of normalizable names can change over time. Apps that key on label or name strings will see identifiers shift underneath them. The only stable identifier is the `node` (the namehash of the name) — but the Subgraph schema surfaces it as the `id` field, and getting this right requires careful, documented handling. See [Use the node as the stable identifier](/docs/integrate/ens-subgraph/backwards-compatibility#use-the-node-as-the-stable-identifier).
Stable IDs that never move
The [Omnigraph API](/docs/integrate/omnigraph) provides stable identification via a Domain’s `id`, a multichain-aware globally unique identifier that works for both ENSv1 and ENSv2 Domains. Domains are also addressable by [InterpretedName](/docs/reference/terminology#interpreted-name), and a Domain’s Canonical Name (`Domain.canonical.name`) is always maximally healed at request time, thanks to [ENSRainbow](/docs/services/ensrainbow).
## Effective ownership is hard to determine
[Section titled “Effective ownership is hard to determine”](#effective-ownership-is-hard-to-determine)
Effective ownership is ambiguous
The Subgraph schema spreads ownership across multiple fields — `owner`, `registrant`, `wrappedOwner` — reflecting raw protocol state (the Registry, the `.eth` Registrar, and the Name Wrapper). Determining the *effective* owner of a domain requires understanding the interplay of all of them. This is exactly the kind of protocol-implementation detail app developers are forced to learn and re-implement, with plenty of room to get it wrong.
One effective owner field
In the [Omnigraph API](/docs/integrate/omnigraph) `Domain.owner` is *always* the effective owner’s address — no weird edge-cases! For ENSv2 Domains, `Domain.owner` is Smart-Account-aware and represents the true owner of the Domain at a given time.
## Missing all offchain ENS names
[Section titled “Missing all offchain ENS names”](#missing-all-offchain-ens-names)
Offchain names are missing entirely
The Subgraph indexes onchain events only, so it has no knowledge of offchain ENS names. A meaningful and growing slice of ENS lives offchain and is entirely absent from Subgraph data.
Automatic offchain name resolution
While the [Omnigraph API](/docs/integrate/omnigraph) doesn’t (*yet!*) index offchain names, it does provide protocol-correct Accelerated Forward Resolution, including support for offchain CCIP-Read-based names.
## Raw “bare-metal” values push the decoding burden onto you
[Section titled “Raw “bare-metal” values push the decoding burden onto you”](#raw-bare-metal-values-push-the-decoding-burden-onto-you)
Decoding is your problem
The Subgraph exposes raw values straight from the ENS protocol, with none of the interpretation that apps actually need:
* **Address records** may be for non-EVM chains and need chain-specific decoding before they’re usable.
* **Contenthash** values are encoded and need decoding to become a usable URL.
* **Text records** are represented by users in many inconsistent ways. Consider the many variations in how someone might set a Twitter/X handle — both the record key and the value vary widely.
Every bit of this interpretation, decoding, and normalization is left to the app developer. The result is more bugs in ENS integrations across the ecosystem, which damages the network effects and growth of ENS.
Interpreted records
The [Omnigraph API](/docs/integrate/omnigraph) supports two major record resolution use-cases:
1. Protocol-accurate ‘raw’ requests, without post processing (`resolve.records`), and
2. Consumer-friendly semantic interpretation of records for, e.g. profile display (`resolve.profile`).
## It can’t cleanly power NFT-reference → avatar use cases
[Section titled “It can’t cleanly power NFT-reference → avatar use cases”](#it-cant-cleanly-power-nft-reference--avatar-use-cases)
Avatar resolution is painful
A common and important flow is taking an NFT reference as input and mapping it: **NFT Ref → Domain → Name → Avatar text record → Avatar image.** This powers services like the ENS Metadata Service, which provides NFT metadata for ENS names using standardized protocols adopted by platforms such as OpenSea, Rarible, Grails, and ENS Vision. The Subgraph’s raw, resolution-free data model makes this flow far harder than it should be.
Automatic avatars
The [Omnigraph API](/docs/integrate/omnigraph) supports automatic avatar URL derivation, including deriving images from NFT references per [ENSIP-12](https://docs.ens.domains/ensip/12); all your app needs to do is render an `