Node Classification & Metadata
ENSIP-27 · ensips/27.md · github.com/ensdomains/ensips/pull/64
A standard way to attach structured, machine-readable metadata to ENS names and subnames — so a DAO, company, or individual can declare what role each node plays (treasury, delegate, person, contract…) and what data it carries, all discoverable on-chain through two new reserved text records.
ENS subnames already model organizational structure in practice — an individual might split coldwallet and hotwallet subnames, a DAO might carve out treasury, smart contracts, working groups, and committees under its top-level name. Today, nothing declares what each subname is for in a machine-readable way. ENSIP-27 fixes that: it lets a node say “I am a Treasury” or “I am a Person”, and attach a JSON Schema describing exactly which additional attributes are expected — enabling dynamic, automated discovery of organizational structure straight from ENS records.
ENSIP-27 reserves exactly two unqualified, global text-record key names. Everything else the spec defines builds on top of these.
class label. A Contract or Wallet designation only declares that node's main purpose within the org structure; the same address can be pointed to by multiple nodes, each with a different class describing a different facet of it.
Twelve recommended values, chosen to maximize cross-ecosystem compatibility. Other values MAY be used for specialized cases — this table is a strong recommendation, not a closed enum.
| class value | meaning |
|---|---|
| Agent | This node represents an autonomous software-controlled entity |
| Application | This node represents a software application, service, or product |
| Committee | This node represents a formal group with delegated authority to make decisions or provide oversight within a defined scope |
| Contract | This node represents, and resolves to, a smart contract |
| Council | This node represents a high-level governance body with broad strategic authority and stewardship responsibilities |
| Delegate | This node represents a voter in on-chain governance, who may or may not have been delegated voting power from others |
| Group | This node represents a logical grouping of multiple child nodes |
| Org | This node represents an organization or sub-organization within a larger entity |
| Person | This node represents an individual human |
| Treasury | This node represents an account for funds that are collectively owned and/or managed |
| Wallet | This node's main purpose is to send, receive, and/or store funds |
| Workgroup | This node represents an operational team focused on executing tasks or work within a specific domain |
The schema value must start with one of three prefixes. Immutable sources are recommended; mutable ones are allowed with a warning.
Followed by the IPFS URI pointing to the JSON payload.
Followed by the schema encoded directly in CBOR format.
Followed by an http(s) URI. Can change or disappear — implementations MUST minimize harm if it does.
- MUST be a flat, single-level JSON object — ENS text records are just string key/value pairs, so no nesting
- Every property MUST be type
"string"and MUST include a"description" - Attribute key names use kebab-case; dot notation for namespacing (per ENSIP-5)
$id(URI, identifies schema creator/version) ·title(should match theclassvalue, or serves as the class identifier if none is set) ·description(explains the org role)- Standard keywords
format,required,default,examples,patternPropertiesare all available
allOf, anyOf, oneOf, if/then/else — SHOULD NOT be used. Broad client support for them can't be assumed.
A schema MAY define a key already reserved elsewhere (e.g. avatar from ENSIP-12) to add context — but MUST NOT contradict its original meaning.
{
"properties": {
"avatar": {
"type": "string",
"description": "The organization's logo, preferably in SVG format. See ENSIP-12 for supported URI schemes."
}
}
}
Three schema-level keywords beyond standard JSON Schema — the mechanism that ties attributes to ENS's specific record model.
Whether the attribute is stored as an ENSIP-5 text record or an ENSIP-24 data record. Omitted → defaults to text.
Governs how a parameterized key's brackets behave — free-form lookup vs. a zero-indexed sequential list. Detailed in Section 6.
If true and no value is set on this node, the client walks up the name hierarchy — parent, then grandparent, and so on — until it finds a value or runs out of ancestors.
schema record — a matching key name is enough. If found, the inherited value satisfies the required keyword for validation. A local value, if present, always wins over inheritance.
A single property can support multiple variant-specific values via bracket notation appended to the base key.
The base form and parameterized form coexist as independent records — base acts as the default when no parameter is requested. Declared with a patternProperties regex:
"patternProperties": {
"^proof-of-humanity(\[[^\]]+\])?$": {
"type": "string",
"description": "A signed proof of humanity attestation. The name of a specific provider can be passed as a parameter.",
"recordType": "data",
"parameterType": "map"
}
}
The parameterType keyword decides how the bracketed value is interpreted:
proof-of-humanity[gitcoin-passport]proof-of-humanity[worldcoin]0 upward until a gap is found; setters MUST NOT leave gaps.member[0]
member[1]
member[2]The spec's own basic example — a schema describing an individual human, combining recordType and inherit.
{
"$id": "https://example.com/schemas/person/v1.0",
"title": "Person",
"description": "This node represents an individual human",
"type": "object",
"properties": {
"first-name": { "type": "string", "description": "The person's first name." },
"last-name": { "type": "string", "description": "The person's last name." },
"proof-of-humanity": {
"type": "string",
"description": "A signed attestation of proof of humanity.",
"recordType": "data"
},
"avatar": {
"type": "string",
"description": "A URL to an image of this person to be used as their profile picture",
"inherit": true
}
}
}
Applied to alice.dao.eth, a subname under a DAO that uses this schema:
| record | value | source |
|---|---|---|
class | Person | local text |
schema | ipfs://bafybei…/person.json | local text |
first-name | Alice | local text |
last-name | Nakamura | local text |
proof-of-humanity | 0x8f3a… (signed attestation) | local data record |
avatar | ipfs://bafkrei…/dao-logo.svg | inherited ↑ from dao.eth |
The motivating case from the spec's Abstract: a DAO models its entire organizational structure in subnames. A client can crawl the tree and know exactly what each node is for — no off-chain coordination required.
graph TD ROOT["dao.eth
class: Org"] TREASURY["treasury.dao.eth
class: Treasury"] VOTES["votes.dao.eth
class: Delegate"] WORK["engineering.dao.eth
class: Workgroup"] ALICE["alice.dao.eth
class: Person
avatar: inherited"] ROOT --> TREASURY ROOT --> VOTES ROOT --> WORK ROOT --> ALICE ALICE -.->|avatar inherited| ROOT classDef rootStyle fill:#F3930B22,stroke:#F3930B,stroke-width:2px classDef nodeStyle fill:#3889FF1F,stroke:#3889FF,stroke-width:1.5px classDef personStyle fill:#D52E7E1F,stroke:#D52E7E,stroke-width:1.5px class ROOT rootStyle class TREASURY,VOTES,WORK nodeStyle class ALICE personStyle
Builds entirely on existing ENSIPs (5, 24). Purely additive — two new optional text-record keys. No breaking changes.
None noted by the authors in the current draft.
Draft · under active discussion. Work sponsored by the ENS Metagov Working Group, soliciting feedback across ecosystems.
Discussion threads referenced in the PR: