Rooms inside a Community are named spaces with their own title, description, optional per-room services (e.g. LiveKit), and profile lists that gate who may post in that room only. Lists are kind 30000 events referenced by a on kind:30223. Communities using rooms MUST not put a tags on the Rooms row of kind 10222 — see Rooms in Community. 30223 is the sole source of truth for room posting ACL.
Tag usage on kind 9 and 30223 is chosen so NIP-29 (Group) kind 9 events can be copied onto a community relay with minimal edits: NIP-29 already puts the group id in h. Here h keeps that role — an opaque room id, not a display name. The community pubkey is carried in c, which NIP-29 chat events lack until you add it when migrating.
Community room definition (kind:30223)
A parameterized replaceable event (NIP-33): the community key (same pubkey as kind 10222) publishes one current definition per room. The d tag is the room id on the wire — the same string as h on kind 9 messages in that room.
Room id (wire) vs display name
The room id (d on 30223, h on kind 9) is an opaque identifier. It SHOULD be random (or otherwise unique) and use only a-z, 0-9, -, _, matching NIP-29 group id rules (Group). Relays and clients that index or enforce by room MUST use this value — e.g. #h on kind 9 for the room bucket on an enforcing relay.
The human-readable title (“Dev chat”, “General”, etc.) lives only in the name tag (and prose in content) on 30223. Do not use the display name as the wire id; do not treat h/d as a slugified title unless you deliberately choose that (it breaks the NIP-29 migration story and collision safety).
Within one community, each d is unique among that community’s 30223 events. Globally, (c, d) identifies a room — c is the community pubkey on 30223 (see below).
Event structure
{
"kind": 30223,
"pubkey": "<community-pubkey>",
"created_at": 1675642635,
"tags": [
["d", "<opaque-room-id>"],
["c", "<community-pubkey>"],
["a", "30000:<pubkey>:<list-d>", "<relay-url>"],
["name", "<human-readable title>"],
// ["image", "<url>"],
// ["livekit", "<url>"]
],
"content": "<long description — markdown or plain text>"
}
Who may post in a room
Profile lists (kind 30000) are standalone events. For room-based chat, posting permission is defined only on kind:30223:
-
One or more
atags (required): an author MAY post if their pubkey is aptag on at least one of the referenced lists (OR across lists). Clients fetch each30000:<pubkey>:<d>and check membership. -
Clients MUST not fall back to
atags on the Rooms row of kind 10222 for kind 9 messages in a room (Community Rooms).
Rooms do not define new join flows: lists still use optional form tags, admin edits p tags, etc., per Profile List.
Room-scoped chat (kind:9)
Messages in a room are kind 9 with:
-
h— opaque room id (same string asdon that room’s 30223). -
c— community pubkey (hex).
Together (c, h) selects exactly one room. This mirrors NIP-29’s h = group id; c scopes the message to your community.
Kind 9 events with h but no c are outside this spec (e.g. plain NIP-29 or legacy shapes) — see Community.
Relay monitors use c for other meanings on other kinds (Relay Discovery). For kind 9, c here is a 64-hex pubkey; still use kinds: [9] (and #c / #h as needed) so monitor events do not mix in.
Event structure
{
"kind": 9,
"pubkey": "<author-pubkey>",
"created_at": 1675642635,
"tags": [
["h", "<opaque-room-id>"],
["c", "<community-pubkey>"]
],
"content": "<message>"
}
Use at most one h and at most one c for this shape. Duplicates are undefined (clients SHOULD reject or apply one rule).
NIP-29 migration
Existing NIP-29 kind 9 events already have h = group id. To adopt this community model:
-
Keep
hunchanged (same opaque id). -
Add
c= your community’s pubkey (hex). -
Publish 30223 with
d= that same id,c= community pubkey (author),alists, andname(and optionalcontent) for the human-facing title and description.
After that, enforcing relays can bucket by #h like NIP-29, while scoping to a community via #c.
Client recommendations
-
For subscriptions:
kinds: [9],#c= community pubkey,#h= room id when following one room. -
Resolve 30223 by community author +
d= messageh, andcmatching the message’s community; enforce Who may post from that event’satags only. -
MAY show
namefrom 30223 in UI; on the wire, identity is(c, h), not the title. -
Discover rooms: kind 30223 by author = community; each event’s
d/name/adescribe one room.
Examples
Example: room definition
{
"kind": 30223,
"pubkey": "3bf0c63fcb93463407fd977870e7a68c66f8868576d639e220416298702c834e",
"created_at": 1735689600,
"tags": [
["d", "k7m2p9xq1n"],
["c", "3bf0c63fcb93463407fd977870e7a68c66f8868576d639e220416298702c834e"],
["name", "Dev chat"],
["a", "30000:3bf0c63fcb93463407fd977870e7a68c66f8868576d639e220416298702c834e:dev-chat-posters", "wss://relay.example.com"],
["livekit", "wss://live.example.com/room-dev"]
],
"content": "Engineering discussion. Be kind."
}
d is opaque; “Dev chat” is only in name.
Example: message in that room
{
"kind": 9,
"pubkey": "97fa5b5c8b3e4c2d1a0f9e8d7c6b5a4938271605f4e3d2c1b0a97867564534231",
"created_at": 1735689700,
"tags": [
["h", "k7m2p9xq1n"],
["c", "3bf0c63fcb93463407fd977870e7a68c66f8868576d639e220416298702c834e"]
],
"content": "Shipped the relay fix — can someone test?"
}
See Also
-
Community — Rooms on kind 10222 uses
k30223 (not 9), noaon that row;hon other kinds = community pubkey -
Group — NIP-29
h= group id (same wire shape as our room id) -
Profile List — kind 30000,
p/form/ list identity
Comments
Public conversation about this article.
No comments yet.
Article metadata
About this entry
Event Id
Raw event
Other authors
No one else has published this topic yet.
