1 Overview
A reaction is a small event that points at another event and carries a short verdict. The verdict is a +, a -, or an emoji. Clients use reactions to build like buttons, upvote counters, and emoji rows.
This NIP defines two event kinds. Kind 7 reacts to a native Nostr event. Kind 17 reacts to external content, such as a website or a podcast episode. Both kinds are ordinary signed events from NIP-01. Any relay can store them. Any client can count them.
A reaction does not modify its target. It only references the target with tags. To count likes for a note, a client subscribes to kind 7 events whose e tag matches the note's id.
Reactions are cheap to publish and cheap to query. One filter — {"kinds":[7],"#e":[<id>]} — returns every reaction to a note that the relay knows about.
#e and renders the count.2 The Reaction Event (Kind 7)
A reaction is a kind 7 event. The content field must hold user-generated content that states the value of the reaction. The convention is +, -, or an emoji.
| Content | Rule | Meaning |
|---|---|---|
| + | MUST | Interpret as a "like" or "upvote". |
| "" | MUST | An empty string. Interpret it the same as +: a like or upvote. |
| - | MUST | Interpret as a "dislike" or "downvote". |
| emoji | SHOULD NOT | An emoji or a NIP-30 custom emoji. Do not interpret it as a like or a dislike. Clients may display the emoji on the post instead. |
Do not fold emoji reactions into your like counter. The spec removed that mapping in 2025. A 🔥 is a 🔥, not an upvote.
3 Tags
A kind 7 reaction locates its target with tags. The tag names come from NIP-01. Their rules here are:
| Tag | Rule | Value and notes |
|---|---|---|
| e | MUST | The id of the event being reacted to. It should carry a relay hint that points to a relay where the target can be found. Extra e tags are not recommended; if present, the target id must be the last e tag. |
| p | SHOULD | The pubkey of the author of the target event. Extra p tags are not recommended; if present, the target pubkey must be the last p tag. |
| a | SHOULD | Only when the target is an addressable event. Set it to the coordinates <kind>:<pubkey>:<d-tag> of the target. Include it together with the e tag, not instead of it. |
| k | MAY | The kind number of the target event as a string, for example "1". |
Hints help clients find the target. The e and a tags should include relay and pubkey hints. The p tags should include relay hints.
The source NIP ships this reference routine. It builds a like for one target event with the recommended tags and hints:
func make_like_event(pubkey: String, privkey: String, liked: NostrEvent, hint: String) -> NostrEvent { var tags: [[String]] = [] tags.append(["e", liked.id, hint, liked.pubkey]) tags.append(["p", liked.pubkey, hint]) tags.append(["k", String(liked.kind)]) let ev = NostrEvent(content: "+", pubkey: pubkey, kind: 7, tags: tags) ev.calculate_id() ev.sign(privkey: privkey) return ev }
The source has two rough edges. First, the Tags section says an e tag "MUST always" be present, but kind 17 external reactions have no Nostr event to point at; the rule can only apply to kind 7. Second, the k tag changes meaning between kinds: on kind 7 it holds a stringified kind number, on kind 17 it holds a NIP-73 external content type such as web.
4 External Content Reactions (Kind 17)
Sometimes the target is not a Nostr event. It is a web page, a podcast, or another external resource. In that case the reaction must be a kind 17 event. It must include NIP-73 external content k and i tags that reference the content. The k tag names the content type. The i tag holds the identifier, with an optional URL hint.
{
"kind": 17,
"content": "⭐",
"tags": [
["k", "web"],
["i", "https://example.com"]
]
}
A target can carry more than one identifier pair. This reaction likes one episode of one podcast. Each i tag adds a URL hint as its third element:
{
"kind": 17,
"content": "+",
"tags": [
["k", "podcast:guid"],
["i", "podcast:guid:917393e3-1b1e-5cef-ace4-edaa54e1f810",
"https://fountain.fm/show/QRT0l2EfrKXNGDlRrmjL"],
["k", "podcast:item:guid"],
["i", "podcast:item:guid:PC20-229",
"https://fountain.fm/episode/DQqBg5sD3qFGMCZoSuLF"]
]
}
5 Custom Emoji Reactions
A client may put a NIP-30 custom emoji :shortcode: in the reaction content. A matching emoji tag maps the shortcode to an image URL. When a shortcode is present, the client should read the emoji tag and render the content as that emoji.
{
"kind": 7,
"content": ":soapbox:",
"tags": [
["emoji", "soapbox", "https://gleasonator.com/emoji/Gleasonator/soapbox.png"]
]
// other fields...
}
Two limits apply. The content can hold only one :shortcode:. The event should carry only one emoji tag.
6 Worked Example
Alice publishes a kind 1 note. Bob likes it. The full reaction event and the relay session look like this:
{
"id": "9d3b1c44f0…",
"pubkey": "8c4f2ab9d1…", // Bob
"created_at": 1756713600,
"kind": 7,
"tags": [
["e", "b1a649ebe8…", "wss://relay.example.com", "22a12a12…"],
["p", "22a12a12…", "wss://relay.example.com"],
["k", "1"]
],
"content": "+",
"sig": "7f0ac2e91b…"
}
-- Bob publishes the reaction -- → ["EVENT", {"id":"9d3b1c44f0…","kind":7,"content":"+", "tags":[["e","b1a649ebe8…","wss://relay.example.com","22a12a12…"], ["p","22a12a12…","wss://relay.example.com"],["k","1"]], …}] ← ["OK", "9d3b1c44f0…", true, ""] -- Alice's client counts reactions to her note -- → ["REQ", "reacts", {"kinds":[7], "#e":["b1a649ebe8…"]}] ← ["EVENT", "reacts", {"id":"9d3b1c44f0…", "kind":7, "content":"+", …}] ← ["EOSE", "reacts"] -- content "+" or "" → like. "-" → dislike. emoji → shown as-is -- → ["CLOSE", "reacts"]
7 Version History
NIP-25 started as a likes-and-dislikes spec for text notes. It grew to cover any event, custom emoji, and external content. Notable changes, newest first:
- 20252025-08Added external content reactions: kind 17 with NIP-73
kanditags. - 20252025-05Removed the rule that mapped emoji reactions to like or dislike. Added relay and pubkey hint recommendations.
- 20252025-02Discouraged extra
eandptags; the target must be the last tag of its letter. - 20242024-05Added the
atag for addressable targets and thekkind tag. A broader "Simplifying reactions" rewrite was reverted in June. - 20232023-08An empty
contentstring now counts as+. - 20232023-07Added custom emoji reactions with NIP-30 shortcodes.
- 20232023-01Allowed emojis to count as likes or dislikes (removed again in 2025).
- 20222022-08Reactions can target any event, not only text notes. The generic like became an explicit
+. - Origin2022-07William Casarin created NIP-25 with likes, dislikes, and downvotes (2022-07-30).
Original Proposal - Credits
| Author | William Casarin |
| First commit | 2022-07-30 — “NIP-25: Reactions” |
| Status | draft - optional — clients choose whether to show or send reactions |
| Source | 25.md on GitHub - full change history |
8 See Also
- NIP-01 — the base event format and the
e,p,atags reused here. - NIP-30 — custom emoji: the
:shortcode:andemojitag format. - NIP-73 — external content IDs: the
kanditags required by kind 17. - NIP-10 — text notes and threads, the most common reaction targets.
- NIP-18 — reposts, the other lightweight response to a note.