1 Overview
Nostr has thousands of event kinds. No single client implements all of them. A social client that meets a music-track event, a poll, or a wiki page needs a way to hand that event to a program that understands it. This NIP defines that hand-off.
It uses two addressable event kinds. A handler information event (kind:31990) is published by an application. It declares which event kinds the application handles and how to open an entity in it. A recommendation event (kind:31989) is published by a user. It says: for this event kind, I recommend these applications.
Discovery then rides on the social graph. When a client meets an unknown kind, it queries for recommendations from the user and the user's follows. Trust in the handler comes from trust in the recommenders, not from an app store.
Three actors take part in the workflow:
- The application publishes
kind:31990with its redirect rules. It does not have to be a separate entity; it can share a pubkey with user A. - User A recommends an app for a specific event kind. User A publishes
kind:31989. - User B seeks a recommendation. User B queries for
kind:31989and then, from the results, queries forkind:31990.
Both kinds sit in the 30000-39999 range, so they are addressable events (NIP-01). A relay keeps only the latest event per pubkey + kind + d-tag value. A user updates a recommendation by publishing a newer event with the same d value.
2 Recommendation Event — kind:31989
User A publishes this event to recommend applications for one event kind:
{
"kind": 31989,
"pubkey": <recommender-user-pubkey>,
"tags": [
["d", <supported-event-kind>],
["a", "31990:app1-pubkey:<d-identifier>", "wss://relay1", "ios"],
["a", "31990:app2-pubkey:<d-identifier>", "wss://relay2", "web"]
]
// other fields...
}
| Tag | Value | Rule |
|---|---|---|
| d | event kind, as a string | The event kind this recommendation covers, for example "31337". |
| a | 31990:<app pubkey>:<d identifier> | Address of one recommended handler event. Multiple a tags can appear on the same event. The next value SHOULD be a relay hint. The value after that SHOULD be the platform where the recommendation applies (for example web, ios). |
3 Handler Information — kind:31990
The application publishes this event. It lists the event kinds the app supports and templates for opening entities in the app:
{
"kind": 31990,
"pubkey": <application-pubkey>,
"content": <optional-kind:0-style-metadata>,
"tags": [
["d", <random-id>],
["k", <supported-event-kind>],
["latest", "35128:<application-pubkey>:<site-d-identifier>", "wss://relay1"],
["next", "35128:<application-pubkey>:<next-site-d-identifier>", "wss://relay1"],
["web", "https://..../a/<bech32>", "nevent"],
["web", "https://..../p/<bech32>", "nprofile"],
["web", "https://..../e/<bech32>"],
["ios", ".../<bech32>"]
]
// other fields...
}
| Item | Rule |
|---|---|
| content | Optional stringified JSON metadata object, in the kind:0 style of NIP-01. Use it when the publishing pubkey is not itself an application. If content is empty, clients use the pubkey's own kind:0 for display data (name, picture, web, LUD16, and so on). |
| d | A random identifier. Unlike kind:31989, it does not carry the supported kind. |
| k | One supported event kind. Repeat the tag for each supported kind that shares the same handler URLs. The same pubkey can also publish multiple kind:31990 events for different apps that handle the same event kind. |
| latest | SHOULD appear on web applications when applicable. Same shape as an a tag: value <kind>:<pubkey>:<d-tag>, then an optional relay hint. References the current kind:35128 site manifest (NIP-5A) for the application. |
| next | Same shape as latest. References the next site manifest intended for rollout or upgrade. |
| web / ios / ... | Platform handler tags. The value is a URL template. <bech32> in the template MUST be replaced by the client with the NIP-19-encoded entity to load. |
An app can register multiple platform tags. A tag can carry a NIP-19 entity name (for example nevent, nprofile) to scope the template to that entity type. A platform tag without an entity name SHOULD be treated as the generic handler for any NIP-19 entity that no other tag handles.
App descriptor events SHOULD tag or otherwise reference their related site manifest events when applicable. Clients can then discover the deployed site manifest for an application directly from the upstream app descriptor.
The source counts tag positions inconsistently. It calls the NIP-19 entity name "the second value of the array", and the relay hint in kind:31989 a tags likewise "the second value" — but in every example these sit in the third array slot, after the tag name and the primary value. Read the positions from the examples, as this page does, not from the prose.
4 The client Tag
When a client publishes an event on behalf of a user, it MAY add a client tag. The tag identifies the client that published the note. It is a tuple of a name, an address that identifies a handler event, and a relay hint for finding that handler event:
{
"kind": 1,
"tags": [
["client", "My Client", "31990:app1-pubkey:<d-identifier>", "wss://relay1"]
]
// other fields...
}
The client tag has privacy implications: it reveals which software a user runs on every event. Clients SHOULD let users opt out of the tag.
5 Discovery Flow
The end-to-end flow works like this. User A uses a nostr app that is not centered on kind:1. User A chooses to announce and recommend that app as a handler for its kind.
Later, user B sees an event of an unknown kind in a social-media-centric client. The client still lets user B interact with the event, for example by tapping on it. The client may then query for handlers recommended by the user and by the user's follows.
Alternative query, bypassing kind:31989
A client can also skip the recommendation step. It queries directly for kind:31990 events whose k tag matches the unknown kind:
→ ["REQ", <id>, { "kinds": [31990], "#k": [<desired-event-kind>], "authors": [...] }]
The direct query drops the social-graph filter. Any pubkey can publish a kind:31990 event. Clients SHOULD apply spam-prevention mechanisms or query high-quality restricted relays, so they do not direct users to malicious handlers.
6 Worked Example
User A uses Zapstr, a client centered on kind:31337 track events. With Zapstr, user A publishes a recommendation of Zapstr as a kind:31337 handler:
{
"kind": 31989,
"tags": [
["d", "31337"],
["a", "31990:1743058db7078661b94aaf4286429d97ee5257d14a86d6bfa54cb0482b876fb0:abcd",
<relay-url>, "web"]
]
// other fields...
}
User B follows user A. In their timeline, user B sees a kind:1 note that tags a kind:31337 event. User B's client does not know how to handle kind:31337, so it displays the event with its alt tag (NIP-31). When user B clicks the event, the client queries for a handler:
-- find recommendations from user B and their follows -- → ["REQ", <id>, { "kinds": [31989], "#d": ["31337"], "authors": [<user>, <users-contact-list>] }] ← ["EVENT", <id>, {"kind":31989, "tags":[["d","31337"], ["a","31990:1743058d…76fb0:abcd", "wss://relay1", "web"]], …}] ← ["EOSE", <id>] -- fetch the a-tagged handler event -- → ["REQ", <id2>, { "kinds": [31990], "authors": ["1743058d…76fb0"], "#d": ["abcd"] }] ← ["EVENT", <id2>, {"kind":31990, "tags":[["d","abcd"],["k","31337"], ["web","https://zapstr.example/track/<bech32>","nevent"]], …}]
User B's client reads the app and handler information from the a-tagged kind:31990 event. It replaces <bech32> in the URL template with the NIP-19 nevent for the track. Then it redirects user B to that URL.
The fallback display in this flow depends on the alt tag from NIP-31, which this manual marks as deprecated. The source text still cites it. Treat alt as a best-effort fallback only. The source also says the client "MIGHT" query the user's and follows' handlers — a non-standard keyword; read it as MAY.
7 Version History
NIP-89 arrived in mid-2023 and has stayed close to its original shape. The client tag and the site-manifest links came later. Notable changes, newest first:
- 20262026-06Added
latestandnexttags linking handler events tokind:35128site manifests (NIP-5A). - 20242024-06Fixed the
REQexamples; assorted JSON and heading formatting fixes through 2026. - 20232023-11Made the
clienttag backwards compatible: the human-readable name moved to the first value, before the handler address. - 20232023-11Added the
clienttag for identifying the publishing client. - 20232023-08Reworded
set_metadatatometadata; removed NIP-33 references after its merge into NIP-01. - Origin2023-06Pablo Fernandez proposed kinds 31989 and 31990 in PR #530.
Original Proposal - Credits
| Author | Pablo Fernandez |
| First commit | 2023-06-07 — “NIP-89: Recommended Application Handlers (#530)” |
| Pull request | nostr-protocol/nips #530 |
| Status | draft - optional |
| Source | 89.md on GitHub - full change history |
8 See Also
- NIP-01 — addressable events,
atags, filters, and thekind:0metadata shape reused incontent. - NIP-19 — the bech32 entities (
nevent,nprofile, …) substituted into handler URL templates. - NIP-31 — the
alttag used to display unknown kinds before a handler is found (deprecated). - NIP-5A — static websites; the
kind:35128site manifests referenced bylatestandnexttags. - NIP-02 — the follow list that scopes recommendation queries to trusted authors.