Picking up where I left off in the last post. I said the protocol was small and that everything in Nostr is "just a signed JSON event with a `kind` field." That sentence is true, but it papers over the most interesting part of the whole system. The `kind` is where Nostr stops being a Twitter clone and starts being something weirder.
This post is me trying to organize what I've learned about event kinds while building on top of them. If you're touching Nostr at the protocol level — writing a client, running a relay, designing a new feature — this is the part you actually need in your head.
The shape of an event
Before the list, the thing itself. Every Nostr event looks like this:
{
"id": "<32-byte sha256 hex>",
"pubkey": "<32-byte hex pubkey>",
"created_at": 1714000000,
"kind": 1,
"tags": [
["e", "<event-id>", "<relay-url>", "reply"],
["p", "<pubkey>", "<relay-url>"]
],
"content": "hello nostr",
"sig": "<64-byte schnorr sig hex>"
}
Seven fields. That's the entire data model. The `kind` is just an integer, and the meaning of `content` and `tags` shifts based on what that integer is. NIPs (Nostr Implementation Possibilities) are how new kinds get standardized.
Kind ranges (the part I wish someone had told me on day one)
Kinds aren't a flat list. They're partitioned into ranges, and the range tells you how relays are supposed to handle the event. This is load-bearing information that's easy to miss:
Range | Behavior |
0 | Replaceable. Only the latest per pubkey is kept. |
1 | Regular. Stored as-is. |
2 | Deprecated (was relay recommendation). |
3 | Replaceable (contact list). |
4-44 | Regular events. Older fixed-purpose kinds. |
1000 – 9999 | Regular. General-purpose application events. |
10000 – 19999 | Replaceable. Latest per (pubkey, kind) wins. Older copies dropped. |
20000 – 29999 | Ephemeral. Relays don't store these. Forward to live subs and drop. |
30000 – 39999 | Addressable. Replaceable per (pubkey, kind, `d` tag). |
That last one — addressable events — is what makes long-form articles, marketplaces, and most of the interesting "other stuff" possible. The `d` tag becomes part of the identity, so one author can have many of them and still update each in place.
The kinds that matter
Not exhaustive. These are the ones I actually run into when reading other people's code or designing my own:
The core social layer
- Kind 0 — Metadata / Profile (NIP-01). The `content` is a JSON string with `name`, `about`, `picture`, `nip05`, `lud16`, etc. Replaceable. This is your profile.
- Kind 1 — Short text note (NIP-01). The tweet. Plaintext in `content`, `e` and `p` tags for replies and mentions.
- Kind 3 — Contact list / follows (NIP-02). Your follow graph. Each `p` tag is someone you follow. Replaceable, so the latest one is the source of truth. Some clients also stuff relay hints in here.
- Kind 5 — Event deletion (NIP-09). A polite request to relays to drop your earlier events. Honored at the relay's discretion. Don't trust it for privacy.
- Kind 6 — Repost (NIP-18). Repost of a kind 1.
- Kind 7 — Reaction (NIP-25). Likes, dislikes, emoji reactions. Content is usually `+`, `-`, or an emoji.
- Kind 16 — Generic repost (NIP-18). Repost of a non–kind-1 event.
Messaging
- Kind 4 — Encrypted DM (NIP-04). The original DM kind. Known to leak metadata (sender, recipient, timing). Being phased out.
- Kind 14 — Chat message (NIP-17). Plaintext inside a sealed gift wrap.
- Kind 13 — Seal, Kind 1059 — Gift wrap (NIP-59). The current best practice for private messaging. Wraps the real event in layers so even the kind is hidden from relays. If you're building DMs today, build on this.
Lightning / zaps
- Kind 9734 — Zap request (NIP-57). Client builds this and hands it to a Lightning wallet to pay an invoice.
- Kind 9735 — Zap receipt (NIP-57). Posted by the recipient's LNURL server after the payment lands. This is the public proof a zap happened.
Lists (NIP-51) — the underrated workhorse
These are mostly in the `10000` and `30000` ranges, which is why they update cleanly
- Kind 10000 — Mute list.
- Kind 10001 — Pin list.
- Kind 10002 — Relay list metadata (NIP-65). Outbox model. Tells the world which relays you read from and write to. If you implement nothing else from NIP-65, implement this — it's how the network scales.
- Kind 10003 — Bookmarks.
- Kind 30000 — Follow sets / categorized people lists.
- Kind 30001 — Generic lists.
- Kind 30003 — Bookmark sets.
Long-form and publishing
- Kind 30023 — Long-form content (NIP-23). Markdown articles. This is what Habla.news uses. The `d` tag is the article slug, so you can edit in place.
- Kind 30024 — Draft long-form content.
Identity, signing, delegation
- Kind 24133 — Nostr Connect / NIP-46. Remote signer protocol. How "bunkers" work, so your key never has to live in the client.
- Kind 22242 — Client authentication (NIP-42). The challenge/response a relay uses to make sure you control the pubkey you claim.
Files, media, and "other stuff"
- Kind 1063 — File metadata (NIP-94). Hash, mime type, dimensions, URL. Used to share files (often paired with Blossom or a similar blob server).
- Kind 24242 — Blossom auth event. Not a NIP, but the de facto auth event for Blossom blob servers. Worth knowing if you're storing media.
- Kind 31922 / 31923 — Calendar events (NIP-52).
- Kind 30402 / 30403 — Marketplace listings (NIP-99).
- Kind 30311 — Live activity (NIP-53). Streams, spaces, live audio.
- Kind 1311 — Live chat message (NIP-53). Chat tied to a live activity.
Relay-side things
- Kind 1984 — Reporting (NIP-56). User-submitted abuse reports. The number is a joke. The mechanism is real.
- Kind 1985 — Labeling (NIP-32). Attach labels to events, pubkeys, or topics. Foundation for moderation and curation that isn't top-down.
The mental model that finally stuck
After staring at this list for too long, here's the framing that made it click for me:
- Kinds 0–7 are the social-network primitives. If you only implement these you have a Twitter clone.
- 10000-range kinds are your settings — your mute list, your relay list, your pins. One per kind per user.
- 20000-range kinds are firehose-only — typing indicators, presence, anything that shouldn't be persisted.
- 30000-range kinds are content with an identity — articles, lists, listings, calendar events. The `d` tag is the slug.
- Everything else is an application carving out a niche.
The protocol doesn't care which kinds you implement. A relay can refuse kinds it doesn't want to host. A client can ignore kinds it doesn't render. Two clients can speak entirely different subsets of kinds and never interact, while sharing the same identity layer underneath. That's the part I keep finding elegant.
What I'm doing with this
In my own work I've been mapping which kinds my project actually needs to speak (read), which it needs to publish (write), and which it just needs to *not break* on when it sees them in the wild. That third bucket is bigger than I expected. Tolerating unknown kinds gracefully is a real design constraint, not an afterthought.
Next post I think I'll dig into the outbox model and NIP-65 specifically — relay discovery is the place where the protocol's elegance bumps hardest into operational reality, and I've been wrestling with it.
Until then, if you want to actually go read the source of truth, the NIPs repo is here: https://github.com/nostr-protocol/nips. It's surprisingly readable.
More soon.