1 —Overview

Node Classification & Metadata

ENSIP-27 · ensips/27.md · github.com/ensdomains/ensips/pull/64

Status: Draft Track: Ecosystem Created 2025-12-15 Contributors: jkm.eth, 1a35e1.eth Sponsored by ENS Metagov Working Group

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.

Builds on ENSIP-5 · Text Records Builds on ENSIP-24 · Data Records JSON Schema 2020-12
2 —Two Reserved Text Records

ENSIP-27 reserves exactly two unqualified, global text-record key names. Everything else the spec defines builds on top of these.

class
Labels the general role or purpose of a node against a larger organizational structure. Mostly for humans deciding what importance to give a node — but also filterable by automated systems.
MUST be PascalCase SHOULD use the taxonomy
class = Person
schema
Points to a JSON Schema describing which additional metadata attributes are expected on this node, stored as ENSIP-5 text records or ENSIP-24 data records.
ipfs:// recommended https:// allowed (mutable)
schema = ipfs://bafybei…/person.json
class is a hint, not access control. Every on-chain address can still send/receive funds, call contracts, publish contracts, and sign — regardless of its 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.
3 —Class Taxonomy

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 valuemeaning
AgentThis node represents an autonomous software-controlled entity
ApplicationThis node represents a software application, service, or product
CommitteeThis node represents a formal group with delegated authority to make decisions or provide oversight within a defined scope
ContractThis node represents, and resolves to, a smart contract
CouncilThis node represents a high-level governance body with broad strategic authority and stewardship responsibilities
DelegateThis node represents a voter in on-chain governance, who may or may not have been delegated voting power from others
GroupThis node represents a logical grouping of multiple child nodes
OrgThis node represents an organization or sub-organization within a larger entity
PersonThis node represents an individual human
TreasuryThis node represents an account for funds that are collectively owned and/or managed
WalletThis node's main purpose is to send, receive, and/or store funds
WorkgroupThis node represents an operational team focused on executing tasks or work within a specific domain
4 —Schema Mechanics

The schema value must start with one of three prefixes. Immutable sources are recommended; mutable ones are allowed with a warning.

ipfs://
Recommended · immutable

Followed by the IPFS URI pointing to the JSON payload.

cbor:
Recommended · immutable

Followed by the schema encoded directly in CBOR format.

https://
Allowed · mutable

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 the class value, or serves as the class identifier if none is set) · description (explains the org role)
  • Standard keywords format, required, default, examples, patternProperties are all available
Composition keywords — allOf, anyOf, oneOf, if/then/elseSHOULD 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.

schema fragment
{
  "properties": {
    "avatar": {
      "type": "string",
      "description": "The organization's logo, preferably in SVG format. See ENSIP-12 for supported URI schemes."
    }
  }
}
5 —Extension Keywords

Three schema-level keywords beyond standard JSON Schema — the mechanism that ties attributes to ENS's specific record model.

recordType
"text" (default) "data"

Whether the attribute is stored as an ENSIP-5 text record or an ENSIP-24 data record. Omitted → defaults to text.

"proof-of-humanity": { "recordType": "data" }
parameterType
"map" (default) "array"

Governs how a parameterized key's brackets behave — free-form lookup vs. a zero-indexed sequential list. Detailed in Section 6.

"member[0]", "member[1]" → array
inherit
bool

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.

"avatar": { "inherit": true }
How inheritance resolves: the ancestor does not need its own 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.
6 —Parameterized Key Names

A single property can support multiple variant-specific values via bracket notation appended to the base key.

base key brackets parameter
proof-of-humanity[gitcoin-passport]
key-name[] ← empty brackets are disallowed (SHALL NOT)

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 fragment
"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:

parameterType: map (default)
parameterType: array
Free-form, arbitrary keys for lookup — any string value is valid.

proof-of-humanity[gitcoin-passport]
proof-of-humanity[worldcoin]
Zero-indexed, sequential list. Clients read from 0 upward until a gap is found; setters MUST NOT leave gaps.

member[0] member[1] member[2]
7 —Worked Example: Person Schema

The spec's own basic example — a schema describing an individual human, combining recordType and inherit.

ipfs://bafybei…/person.jsontitle: "Person"
{
  "$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:

recordvaluesource
classPersonlocal text
schemaipfs://bafybei…/person.jsonlocal text
first-nameAlicelocal text
last-nameNakamuralocal text
proof-of-humanity0x8f3a… (signed attestation)local data record
avataripfs://bafkrei…/dao-logo.svginherited ↑ from dao.eth
8 —Why It Matters: An Org Tree

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
9 —Status, Compatibility & Links

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:

This is a proposal, not a shipped standard.
ENSIP-27 is in draft status and open for review — nothing here is deployed or finalized yet.
View PR #64 on GitHub
ensdomains/ensips · PR #64 · ensips/27.md · generated explainer, not part of the ENSIP itself