1 Overview
This NIP defines groups that only a closed set of users can write to. A group can be public for reading by external users, or private. The rules are not enforced by cryptography. They are enforced by a relay: the relay accepts or rejects each event based on the group's membership and settings.
A group is identified by a random string of any length. This string is the group id. There is no protocol-level act of group creation. A relay, most likely when asked by users, creates rules around a specific id. From then on, events sent to that id are subject to those rules, and the id serves as an actual group.
Members publish ordinary events into the group. A tag named h carries the group id. Admins publish moderation events (kinds 9000 to 9020) to add users, remove users, and change settings. The relay itself publishes metadata events (kinds 39000 to 39005) that describe the current group state. Users ask to join with kind 9021 and to leave with kind 9022.
Normally a group belongs to one specific relay. The community may move the group to another relay. It may even fork the group, so the same id exists in different forms across different relays.
Coming from NIP-28 public chat channels? Think of NIP-29 as the managed upgrade. Channels have no gatekeeper; here the relay is the gatekeeper, and the admins hold the keys.
The source spec states "there is no way to create a group", yet the moderation table defines kind 9007 as create-group. Read the first statement as: no protocol object creates a group; a relay decides to honor an id, and kind 9007 is one way to ask it to.
2 Group Identity, Migration, and Forks
Two things hold a group together: the group id, and the relay that currently enforces access rules over that id on behalf of one or more owners or admins. Enforcement is tied to one relay. The group is only as durable as that relay's willingness and ability to keep hosting it.
Moving a group to another relay
The hosting relay can become unresponsive, go offline permanently, or become untrustworthy. Then the group can migrate. Copy the existing events to a different relay. The new relay must agree to receive the old group, to continue enforcing the same rules, and to acknowledge the same admins.
Forking a group
Members of a group can disagree. A dissident faction can take the group's history to a different relay that agrees to change the rules and assign admin powers to a different set of users. This is a group fork. The new relay keeps the same id but enforces different moderation policies and a different admin roster.
Forks are not bugs. It is a feature of this NIP that one id can identify different communities at the same time. Each fork lives on its own relay, with its own kind:39000 metadata event, its own kind:39001 admin list, and its own message history.
The fork section of the source opens with "a situation in which it might be desirable to move a relay to a different group". The wording is reversed; the intent, per the rest of the section, is moving a group to a different relay.
Detecting migrations and forks
Clients SHOULD periodically check for migrations and forks. They MUST check when their primary relay for a group is offline or unreachable. To check, read the kind:10009 group-list events of the group's admins and of trusted friends. Cache the admin pubkeys of your groups locally, so the check works while the original relay is down.
A kind:10009 entry can point to a different relay hint than the one the client was using. Then the client SHOULD notify the user that the group may have moved or forked. It SHOULD offer to fetch the group's events from the new relay. If the user agrees, it SHOULD migrate the user's participation and update the user's own kind:10009.
Replicating a group for resilience
A group that must not lose history SHOULD keep a live backup replica of all its events on a second relay. The replica relay does not need to enforce any moderation rules. It exists only to preserve history. If the primary relay disappears, the community picks a new authoritative relay and copies the historical events over from the replica.
3 Referencing a Group
Relays generate the events that describe group metadata and group admins. These are addressable events (see NIP-01) signed directly by the relay keypair, with the group id as the d tag.
Identify a group with a NIP-19 naddr1... code to its kind:39000 metadata event. Set the public key to the relay's self public key, the d identifier to the group id, the kind to 39000, and add a relay hint to the hosting relay.
An invite code may be appended to the group identifier:
naddr1...?invite=<code>
Clients should place this code in the code tag of the kind:9021 join request. The bech32 charset does not include ?. So everything before the ? is still a valid identifier on its own. Clients that do not understand the suffix can ignore it.
4 The h Tag and Timeline References
Every event a user sends to a group — chat messages, text notes, moderation events, and so on — MUST carry an h tag with the value set to the group id.
| Tag | Used in | Meaning |
|---|---|---|
| h | user-created events, kinds 9000-9022 | Group id. Marks the event as sent to the group. |
| d | relay-generated kinds 39000-39005 | Group id as the addressable-event identifier. Marks the event as about the group. |
The previous tag
Group events may reference previous events seen on the same relay, in a previous tag. This stops events from being reused out of context. The client picks which previous events to cite. Each reference is the first 8 characters (4 bytes) of the id of any event among the last 50 events the user saw on the relay, excluding the user's own events. Zero or more references are allowed. Clients are recommended to include at least 3, and relays are recommended to enforce that.
["previous", "eb96c864", "2db75638", "b5d1065f"]
This is a hack. It prevents messages from being broadcast to external relays that host out-of-context forks of the group. Relays are expected to reject any event whose timeline references are not found in their own database. Clients should also check the references, to keep relays honest.
Late publication
Relays should prevent late publication: messages published now with a timestamp from hours or days ago. The one exception is a relay that is open to receive a group forked or moved from another relay.
5 Roles and Group Management
A group can have any number of users with elevated access. Role labels identify these users. Relays define the labels arbitrarily (see kind:39003 in §8). This NIP does not define what each role can do. That is relay policy, and it can vary.
A user with the right capability assigns roles by publishing a kind:9000 event. Put the target user's pubkey in a p tag, and the roles after it. This works even when the target is already a member: issue the kind:9000 again and the relay must simply update the user's roles.
The roles that carry a real privilege should be listed in the relay's kind:39003 event. The relay may also accept other role names, arbitrarily defined by clients, and just do nothing with them. Any user whose roles carry any privilege counts as an admin in the broad sense, and may be listed in the group's kind:39001 event.
6 User Events: Join and Leave
A group accepts normal user-created events of any kind: chats, threads, long-form articles, calendar events, livestreams, market announcements, and so on. Each keeps the format from its own NIP, plus the h tag. Two special kinds let a user manage their own membership. Both require the h tag.
Join request (kind 9021)
Any user can send a kind:9021 event to the relay to request admission. The relay MUST reject the request while the user has not been added to the group. The error message SHOULD say whether the rejection is final, whether the request is pending review, or whether special handling applies (for example, payment is required). If the user is already a member, the relay MUST reject the event with duplicate: as the error message prefix.
{
"kind": 9021,
"content": "optional reason",
"tags": [
["h", <group-id>],
["code", <optional-invite-code>]
]
}
The optional code tag lets the relay preauthorize acceptance. It pairs with the kind:9009 create-invite moderation event.
Leave request (kind 9022)
Any user can send a kind:9022 event to be removed from the group automatically. The relay answers by issuing a kind:9001 event that removes the user.
{
"kind": 9022,
"content": "optional reason",
"tags": [
["h", <group-id>]
]
}
kind:9000 and mirrors the new state in kind:39002.7 Moderation Events (Kinds 9000-9020)
Moderation events change group state. They are optional. The relay master key or a group admin sends them, and relays should reject them when the author is not an authorized admin. They require the h tag. The relay must check that the sending pubkey can perform the given action, based on its role and the relay's internal policy (see kind:39003).
{
"kind": 90xx,
"content": "optional reason",
"tags": [
["h", <group-id>],
["previous", ...]
]
}
Each action uses a different kind. Each takes its arguments as tags:
| Kind | Name | Argument tags |
|---|---|---|
| 9000 | put-user | p with pubkey hex, then optional roles. Adds a member or updates roles. |
| 9001 | remove-user | p with pubkey hex. |
| 9002 | edit-metadata | All the fields of the group-metadata event (kind:39000). |
| 9005 | delete-event | e with event id hex. |
| 9007 | create-group | none. |
| 9008 | delete-group | none. |
| 9009 | create-invite | arbitrary code. |
| 9010 | update-pin-list | Zero or more e with event id hex or a with event address. |
The group state must be fully reconstructable from the canonical sequence of these events: who is a member, who is an admin and with which permission, the group name and picture, and so on.
The range is stated as kinds 9000-9020, but only eight kinds are defined. Kinds 9003, 9004, 9006, and 9011-9020 are reserved gaps with no meaning; 9006 (edit-group-status) existed historically and was removed in 2024. Do not assign these numbers new meanings inside groups.
8 Relay-Generated Metadata (Kinds 39000-39005)
These events carry the group id in a d tag, not an h tag. Only the relay master key MUST create them — the key that the NIP-11 document states as the "self" pubkey. For each group, a single instance of each kind (or none) should exist at all times. The events are informative only, but they should reflect the latest group state as changed by the moderation events over time.
Group metadata (kind 39000, optional)
Defines how clients should display the group. The relay where the event is found must generate and sign it. Relays should not accept a kind:39000 signed by anyone else. If the group is forked across relays, each relay carries its own version of this event.
{
"kind": 39000,
"content": "",
"tags": [
["d", <group-id>],
["name", "Pizza Lovers"],
["picture", "https://pizza.com/pizza.png"],
["banner", "https://pizza.com/banner.png"],
["about", "a group for people who love pizza"],
["private"],
["closed"],
["supported_kinds", "9", "11"]
]
// other fields...
}
| Tag | Meaning when present | Meaning when omitted |
|---|---|---|
| name | Display name of the group. | |
| picture | Group picture URL. | |
| banner | Group banner image URL. | |
| about | Short group description. | |
| private | Only members can read group messages. | Anyone can read group messages. |
| restricted | Only members can write messages to the group. | Anyone can send messages to the group. |
| hidden | Relays should hide group metadata from non-members. | Anyone can request the group metadata events. |
| closed | Join requests are ignored. | Users can expect join requests to be honored. |
| livekit | The group supports LiveKit-powered media rooms (live audio/video). See §9. | No AV support advertised. |
| supported_kinds | List of stringified kind numbers the group supports. An empty list means no kinds are supported (an AV-only group, for example). | All kinds are assumed supported. |
| parent | The group is a subgroup of the referenced group. See §10. | The group is a root group. |
| child | Ordered list entries naming the group's subgroups. See §10. | The group has no subgroups. |
Group admins (kind 39001, optional)
Lists each admin along with one or more roles. The roles SHOULD correspond to the roles the relay supports, as advertised by the kind:39003 event.
{
"kind": 39001,
"content": "list of admins for the pizza lovers group",
"tags": [
["d", <group-id>],
["p", <pubkey1-as-hex>, "ceo"],
["p", <pubkey2-as-hex>, "secretary", "gardener"]
// other pubkeys...
]
}
Group members (kind 39002, optional)
A list of member pubkeys, each in a p tag. Relays may choose not to publish this information, to restrict which pubkeys can fetch it, or to display only a subset of the members. Clients should not assume this event is always present, or that it contains the full member list.
{
"kind": 39002,
"content": "list of members for the pizza lovers group",
"tags": [
["d", <group-id>],
["p", <admin1>],
["p", <member-pubkey1>],
["p", <member-pubkey2>]
// other pubkeys...
]
}
Group roles (kind 39003, optional)
The relay MAY publish this event to tell users and clients which roles it supports in its internal logic. Each role is a role tag with a role name and an optional description.
Example: a relay supports the roles "admin" and "moderator". The "admin" can edit group metadata, delete messages, and remove users. The "moderator" can only delete messages. The relay could just as well call these roles "ceo" and "secretary"; the exact name is not relevant. How the relay decides its roles and how it handles moderation events based on them is specific to each relay and not specified here.
{
"kind": 39003,
"content": "list of roles supported by this group",
"tags": [
["d", <group-id>],
["role", <role-name>, <optional-description>],
["role", <role-name>, <optional-description>]
// other roles...
]
}
LiveKit participants (kind 39004, optional)
The relay MAY publish this event to say who is live in an AV room. The relay should update it whenever a user joins or leaves. Clients are expected to keep an active subscription to it. The event MUST contain zero or more participant tags, each with a participant's public key as lowercase hex.
{
"kind": 39004,
"content": "",
"tags": [
["d", <group-id>],
["participant", <pubkey>]
// other participants...
]
}
Group pinned events (kind 39005, optional)
A list of events pinned in the group, in display order. The kind:9010 moderation event carries the full list: e tags for regular events and a tags for addressable events. Pinning, unpinning, reordering, and clearing pins all work the same way — submit a new full list. When the relay accepts a kind:9010, it regenerates this event to mirror it. The relay may limit the number of pins. Clients may display the pinned events at the top of the group view.
{
"kind": 39005,
"content": "",
"tags": [
["d", <group-id>],
["e", <event-id1-as-hex>],
["a", <kind>:<pubkey>:<d-identifier>],
["e", <event-id2-as-hex>]
// other event references...
]
}
9 Live Audio/Video Spaces
A group may support live audio and/or video chat through a LiveKit server. It signals this with the livekit tag in the group's kind:39000 announcement event. A group that supports only live AV chat, and no text messages, should use an empty supported_kinds tag. This allows both Discord-style setups (independent AV rooms and text rooms) and Telegram-style setups (one room with both text and AV).
To join the AV space after seeing a livekit tag, the client calls the token endpoint at the relay path /.well-known/nip29/livekit/<group-id>. The call carries a NIP-98-style Authorization header. The relay returns the LiveKit JWT and the URL of the LiveKit server. The client then proceeds with the standard LiveKit flow.
The Authorization event is a kind:27235 event. It must contain the tag ["u", "https://relay.tld/.well-known/nip29/livekit/<group-id>"].
- Relays should enforce access control at JWT creation time, according to the group settings.
- Relays MUST set the JWT
subproperty (called "identity" in standard LiveKit libraries) so that its first 64 characters are the user's lowercase hex public key. One user can hold several JWTs and join the same room several times, so relays are expected to append a random identifier after those 64 characters. - To advertise AV support (so clients can offer the option when users create or edit groups), a relay should serve HTTP status
204athttps://relay.tld/.well-known/nip29/livekit.
10 Subgroups
Groups MAY form a hierarchy. A subgroup is an ordinary NIP-29 group: same kind:39000, same moderation events, same h tag mechanics. The only difference: its metadata carries a parent tag that points at the parent group's d identifier. A group without a parent tag is a root group.
// root group { "kind": 39000, "tags": [["d", "tech"], ["name", "Tech"], ["child", "nostr"]] } // subgroup { "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "tech"], ["child", "nip29"]] } // sub-subgroup { "kind": 39000, "tags": [["d", "nip29"], ["name", "NIP-29"], ["parent", "nostr"]] }
To build the tree, fetch {"kinds":[39000]} and assemble locally. Events without a parent tag are roots. Group the rest under the d value their parent tag references. The tree is scoped to a single relay. When aggregating across relays, a client MAY see conflicting parent values for the same d. Then the kind:39000 from the relay currently serving the group's events is authoritative.
parent and child tags link them.Relay support detection
A relay that supports subgroups SHOULD advertise it in its NIP-11 relay information document, under a nip29 object:
{
"supported_nips": [29],
"nip29": { "subgroups": true }
}
Subgroup rules
- Parenting, re-parenting, and promotion to root all go through a
kind:9002(edit-metadata) event from an authorized admin. Include the desiredparentvalue, or omit theparenttag to detach. The relay then updates the group'skind:39000. - The relay also updates the parent's
kind:39000to include the new child as achildtag, and vice-versa. When a group switches parents, the relay updates the previous parent as well. - Historical events under the group's
htag stay valid across these transitions. - A
kind:9002MAY carry at most oneparenttag. The resultingkind:39000therefore also carries at most oneparenttag. - Absence of the
parenttag makes the group a root. - Relays MUST reject a
kind:9002whoseparentvalue would create a cycle, including self-reference. - If the declared parent does not exist on the relay, the relay MUST reject the
kind:9002. - When a parent is deleted with
kind:9008, its remaining children automatically become roots. - Relays MUST reject any
kind:9002carrying["parent", "X"]unless the event author is also an admin ofX, perX'skind:39001. - A parent's
kind:39000MUST include["child", "<id>"]tags as an ordered list of its children. The position of each tag in the array is the order. By default the relay appends new children as they are created. The parent's admin can rearrange the list with akind:9002carrying the desiredchildtags in order; each edit replaces the previous list. Relays MUST reject akind:9002metadata edit that does not include all the children aschildtags.
// "social" adopts "nostr" (was under "tech") → { "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"], ["parent", "social"]] } // Relay emits: ← { "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "social"]] } ← { "kind": 39000, "tags": [["d", "social"], ["child", "nostr"]] } // Admin promotes "nostr" to root: edit-metadata without a parent tag → { "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"]] } // Relay emits: ← { "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"]] } ← { "kind": 39000, "tags": [["d", "social"]] }
Subgroup membership
Each subgroup has its own independent membership. Users join through kind:9021 and kind:9000, like any other group. Nothing is inherited across the parent-child link. Membership does not cascade: parent membership grants no membership in a child. Each group's member list is defined only by its own kind:9000 and kind:9001 events. Admin roles do not inherit either. Each group's kind:39001 is authoritative for its own scope. A parent admin has no authority over a child unless the child's own kind:39001 lists the same pubkey.
This spec defines no automatic propagation of membership between parent and child. A relay that wants such behavior MAY emit the corresponding kind:9000/kind:9001 moderation events itself. Clients MUST NOT assume it: no kind:9000 in a child means the user is not a member of that child, regardless of parent membership.
11 Implementation Notes
Checking your own membership
Look at the latest of either kind:9000 or kind:9001 events for your pubkey in a group. The latest one tells you whether you are currently a member or were removed. If neither exists, assume you are not a member.
Adding yourself to a group
Anyone can send a kind:9021 join request. The relay may generate a kind:9000 immediately, or defer the decision to an administrator. If a group is closed, join requests are not honored unless they include an invite code.
Storing your list of groups
NIP-51 defines kind:10009: a list where clients store the groups a user wants to remember being in. It is also the anchor for fork and migration detection (§2).
12 Worked Example
Alice joins the group pizza on wss://groups.example.com with an invite code, posts a chat message, and later leaves. Orange lines go to the relay. Blue lines come from the relay.
-- Alice requests to join with the invite code from naddr1...?invite=BEACH -- → ["EVENT", {"kind":9021,"pubkey":"a1ec3f77…","content":"pineapple tolerated?", "tags":[["h","pizza"],["code","BEACH"]], …}] ← ["OK", "8c41d0aa…", true, ""] -- the relay admits her and mirrors the new state -- ← ["EVENT", "sub1", {"kind":9000,"pubkey":"<relay-self-key>", "tags":[["h","pizza"],["p","a1ec3f77…","member"],["previous","eb96c864","2db75638","b5d1065f"]], …}] ← ["EVENT", "sub1", {"kind":39002,"pubkey":"<relay-self-key>", "tags":[["d","pizza"],["p","f7a2bd4c…"],["p","a1ec3f77…"]], …}] -- Alice posts a chat message with timeline references -- → ["EVENT", {"kind":9,"content":"margherita night!", "tags":[["h","pizza"],["previous","2db75638","b5d1065f","8c41d0aa"]], …}] ← ["OK", "77b0c2e1…", true, ""] -- a second join attempt is rejected -- → ["EVENT", {"kind":9021,"tags":[["h","pizza"]], …}] ← ["OK", "91aa07f3…", false, "duplicate: already a member"] -- Alice leaves; the relay auto-issues the removal -- → ["EVENT", {"kind":9022,"content":"diet","tags":[["h","pizza"]], …}] ← ["OK", "cd80413b…", true, ""] ← ["EVENT", "sub1", {"kind":9001,"pubkey":"<relay-self-key>", "tags":[["h","pizza"],["p","a1ec3f77…"]], …}]
13 Version History
NIP-29 has been reworked heavily since it landed. Notable changes, newest first:
- 20262026-08Added the
previoustag example and clarified group migration, fork detection viakind:10009, and replica relays. - 20262026-07Big feature wave: subgroups with
parent/childtags, message pinning (kind:9010+kind:39005, withatags), the?invite=identifier suffix, and thebannermetadata tag. - 20262026-05Added LiveKit audio/video spaces: the
livekittag, the token endpoint, JWT rules, andkind:39004participants. - 20262026-01Deleted the "unmanaged groups" concept, clarified the relay
selfkey, and dropped a poorly-thought group identifier recommendation. - 20252025-12Clarified
closedvsprivate, added thehiddenmetadata tag. - 20242024-11Chat and thread kinds were broken out into their own NIPs;
kind:9000was renamedput-user; duplicate joins now get aduplicate:rejection. - 20242024-09Fine-grained permissions were reworked into relay-defined roles; invites simplified into the
codetag withkind:9009. - 20242024-07Added
kind:9007create-group,kind:9008delete-group, andkind:9022leave requests. - 20242024-02fiatjaf landed "Simple Groups" as NIP-29 (PR #566), replacing an earlier draft in this slot.
- Origin2023-11Jon Staab created the file at this number for an image-metadata draft; that content later moved to NIP-92.
Original Proposal - Credits
| Author | fiatjaf — PR #566, "NIP-29: Simple Groups" (2024-02-28) |
| First commit | 2023-11-27 — "Add imeta tag" (Jon Staab; the slot then held an unrelated draft that moved to NIP-92) |
| Status | draft - optional - relay feature — the relay does the enforcement |
| Source | 29.md on GitHub - full change history |
14 See Also
- NIP-01 — the event format, addressable events, and the
OKrejection prefixes used here. - NIP-11 — the relay information document; source of the relay
selfpubkey and thenip29subgroups flag. - NIP-19 — the
naddrencoding used to reference a group. - NIP-51 — defines
kind:10009, the user's list of groups. - NIP-98 — HTTP auth with
kind:27235, used for the LiveKit token endpoint. - NIP-28 — the older, unmanaged public chat channels this NIP supersedes in practice.