clone

every nostr nip as a chapter of an illustrated, early-2000s printed game manual

main
nip.guide / site / nips / 50.html
50.html 19.0K
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-50 - Search Capability — Nostr Instruction Manual</title>7<meta name="description" content="NIP-50: the search filter field. Free-text queries in REQ messages, relevance ranking, spam filtering, and key:value query extensions. Complete manual page.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 50 - Relay Ops</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="50">50</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 50</div>50 <h1>Search Capability</h1>51 <p class="sub">A free-text <code>search</code> field for REQ filters, so a client can ask a relay for events that match a human query.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>The <a href="01.html">NIP-01</a> filter is structured. It matches exact ids, exact pubkeys, exact kinds, and exact tag values. It cannot answer a question like "best nostr apps". Many use cases need that kind of general search.</p>60 <p>This NIP adds one field to the filter object: <code>search</code>. The field carries a human-readable query string. The relay interprets the query and returns matching events. How the relay matches is up to the relay. Search algorithms differ between event kinds and between implementations. This NIP defines only the extensible frame around them.</p>61 <p>The frame has two parts. First, rules for how relays and clients handle the <code>search</code> field. Second, an extension syntax: <code>key:value</code> pairs inside the query string that refine the search.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>Search is a relay feature, not an event format. This NIP defines no new kinds, no new tags, and no new messages. It only extends the filter object from <a href="01.html">NIP-01</a>.</p>66 </div>6768 <h2 class="bar" id="spec"><span class="num">2</span> The <code>search</code> Filter Field</h2>69 <p>A client adds the <code>search</code> field to any filter inside a <code>REQ</code> message:</p>7071 <div class="crt">72 <div class="titlebar">Filter object - new field</div>73<pre>{74 <span class="c">// other fields on filter object...</span>75 <span class="k">"search"</span>: <span class="c">&lt;string&gt;</span>76}</pre>77 </div>7879 <p>The value is a query in human-readable form, for example <code>"best nostr apps"</code>. It is not a regular expression and not a structured language. The relay decides what "matches" means.</p>80 <p>The <code>search</code> field combines with the normal filter fields. A client may include <code>kinds</code>, <code>ids</code>, and any other field to restrict the results to particular events. A client may also send several search filters in one <code>REQ</code>:</p>8182 <div class="crt">83 <div class="titlebar">Two search filters in one REQ</div>84<pre><span class="send">["REQ", "", { <span class="k">"search"</span>: <span class="s">"orange"</span> }, { <span class="k">"kinds"</span>: [1, 2], <span class="k">"search"</span>: <span class="s">"purple"</span> }]</span></pre>85 </div>8687 <h2 class="bar" id="relay"><span class="num">3</span> Relay Behavior</h2>88 <p>A relay that receives a search filter follows these rules:</p>89 <div class="tscroll"><table class="ref">90 <caption>Relay rules for search filters</caption>91 <tr><th>Rule</th><th>Level</th><th>Detail</th></tr>92 <tr><td class="btn">interpret</td><td>SHOULD</td><td>Interpret the query to the best of its ability and return events that match it.</td></tr>93 <tr><td class="btn">match content</td><td>SHOULD</td><td>Match the query against the <code>content</code> field of events.</td></tr>94 <tr><td class="btn">match other fields</td><td>MAY</td><td>Match against other fields when that makes sense for a specific kind.</td></tr>95 <tr><td class="btn">rank</td><td>SHOULD</td><td>Return results in descending order by quality of match, as the implementation defines it. Not by <code>created_at</code>.</td></tr>96 <tr><td class="btn">limit</td><td>SHOULD</td><td>Apply the <code>limit</code> filter after sorting by matching score.</td></tr>97 <tr><td class="btn">ignore extensions</td><td>SHOULD</td><td>Ignore <code>key:value</code> extensions it does not support. See §5.</td></tr>98 <tr><td class="btn">exclude spam</td><td>SHOULD</td><td>Exclude spam from search results by default, if the relay supports some form of spam filtering.</td></tr>99 </table></div>100101 <div class="box warn">102 <span class="tag">Warning!</span>103 <p>Search results break the normal ordering rule. A plain <a href="01.html">NIP-01</a> query returns the newest events first. A search query returns the best matches first. Do not assume the first result is the most recent one.</p>104 </div>105106 <figure class="diagram">107 <div class="frame">108 <svg viewBox="0 0 560 120" role="img" aria-label="Query string flows through extension parsing, content matching, score ranking, and the limit cut">109 <rect class="d-actor" x="10" y="35" width="110" height="50" rx="8"/>110 <text class="d-actor-label" x="65" y="57" text-anchor="middle">Query</text>111 <text class="d-note" x="65" y="74" text-anchor="middle">"search" string</text>112 <rect class="d-actor" x="160" y="35" width="110" height="50" rx="8"/>113 <text class="d-actor-label" x="215" y="57" text-anchor="middle">Extensions</text>114 <text class="d-note" x="215" y="74" text-anchor="middle">key:value pairs</text>115 <rect class="d-actor" x="310" y="35" width="100" height="50" rx="8"/>116 <text class="d-actor-label" x="360" y="57" text-anchor="middle">Match</text>117 <text class="d-note" x="360" y="74" text-anchor="middle">content field</text>118 <rect class="d-actor" x="450" y="35" width="100" height="50" rx="8"/>119 <text class="d-actor-label" x="500" y="57" text-anchor="middle">Rank</text>120 <text class="d-note" x="500" y="74" text-anchor="middle">score, then limit</text>121 <path class="d-msg d-flow" d="M122,60 L157,60"/>122 <path class="d-msg d-flow" d="M272,60 L307,60"/>123 <path class="d-msg d-flow" d="M412,60 L447,60"/>124 </svg>125 </div>126 <figcaption>Fig. 50-1 — <b>Search pipeline.</b> The relay parses extensions, matches content, ranks by score, then applies <code>limit</code>.</figcaption>127 </figure>128129 <h2 class="bar" id="client"><span class="num">4</span> Client Behavior</h2>130 <p>Not every relay implements search. A relay that does lists the number 50 in the <code>supported_nips</code> field of its <a href="11.html">NIP-11</a> information document. Clients follow these rules:</p>131 <ul>132 <li>Clients SHOULD use the <code>supported_nips</code> field to learn if a relay supports the <code>search</code> filter.</li>133 <li>Clients MAY send search filters to any relay, if they are prepared to filter out extraneous responses from relays that do not support this NIP.</li>134 <li>Clients SHOULD query several relays that support this NIP. This compensates for different implementation details between relays.</li>135 <li>Clients MAY verify that returned events match the query, in a way that suits the client's use case.</li>136 <li>Clients MAY stop querying relays that have low precision.</li>137 </ul>138139 <div class="box hint">140 <span class="tag">Hint!</span>141 <p>A relay that does not know this NIP ignores the unknown <code>search</code> field. It then matches the rest of the filter. An almost empty filter matches almost everything. Expect a flood of unrelated events from such relays, and filter them out on your side.</p>142 </div>143144 <figure class="diagram">145 <div class="frame">146 <svg viewBox="0 0 560 200" role="img" aria-label="Client fans one search query out to three relays, merges ranked results, and drops a low-precision relay">147 <rect class="d-actor" x="10" y="75" width="110" height="50" rx="8"/>148 <text class="d-actor-label" x="65" y="104" text-anchor="middle">Client</text>149 <rect class="d-actor" x="250" y="14" width="110" height="44" rx="8"/>150 <text class="d-actor-label" x="305" y="41" text-anchor="middle">Relay 1</text>151 <rect class="d-actor" x="250" y="78" width="110" height="44" rx="8"/>152 <text class="d-actor-label" x="305" y="105" text-anchor="middle">Relay 2</text>153 <rect class="d-actor" x="250" y="142" width="110" height="44" rx="8"/>154 <text class="d-actor-label" x="305" y="169" text-anchor="middle">Relay 3</text>155 <path class="d-msg d-flow" d="M122,88 C180,72 190,48 248,40"/>156 <path class="d-msg d-flow" d="M122,100 L248,100"/>157 <path class="d-msg d-flow" d="M122,112 C180,128 190,152 248,160"/>158 <path class="d-msg blue" d="M362,40 C420,44 430,70 440,86 C400,80 380,74 364,52"/>159 <text class="d-label" x="185" y="66" text-anchor="middle">REQ + search</text>160 <text class="d-note" x="452" y="44">ranked results, merged</text>161 <text class="d-note" x="452" y="169">low precision? stop asking</text>162 <circle r="4" fill="#f26a00">163 <animateMotion dur="2.6s" repeatCount="indefinite" path="M122,100 L248,100"/>164 </circle>165 </svg>166 </div>167 <figcaption>Fig. 50-2 — <b>Fan-out and verify.</b> The client queries several search relays, merges results, and drops relays with low precision.</figcaption>168 </figure>169170 <h2 class="bar" id="extensions"><span class="num">5</span> Extensions</h2>171 <p>A query string may contain <code>key:value</code> pairs: two words separated by a colon. Each pair is an extension. Extensions refine the search. A relay SHOULD ignore extensions it does not support. A relay MAY support these extensions:</p>172173 <div class="tscroll"><table class="ref">174 <caption>Query extensions</caption>175 <tr><th>Extension</th><th>Effect</th></tr>176 <tr><td class="btn">include:spam</td><td>Turn off spam filtering, if the relay enabled it by default.</td></tr>177 <tr><td class="btn">domain:&lt;domain&gt;</td><td>Include only events from users whose valid <a href="05.html">NIP-05</a> domain matches the domain.</td></tr>178 <tr><td class="btn">language:&lt;code&gt;</td><td>Include only events of the specified language. The value is a two-letter ISO 639-1 language code.</td></tr>179 <tr><td class="btn">sentiment:&lt;value&gt;</td><td>Include only events of a specific sentiment. The value is <code>negative</code>, <code>neutral</code>, or <code>positive</code>.</td></tr>180 <tr><td class="btn">nsfw:&lt;true/false&gt;</td><td>Include or exclude nsfw events. The default is <code>true</code>: nsfw events are included.</td></tr>181 </table></div>182183 <div class="box tech">184 <span class="tag">Tech Note</span>185 <p>The extension syntax is ambiguous. Any two words separated by a colon count as an extension. The spec gives no escape rule for a literal colon in the query text. A search for <code>re:invent</code> may vanish as an unsupported extension on one relay and match text on another. There is also no way to list which extensions a relay supports; a relay silently drops the ones it does not know.</p>186 </div>187188 <h2 class="bar" id="examples"><span class="num">6</span> Worked Example</h2>189 <p>The session below shows one search round trip against a relay that supports this NIP. Orange lines go to the relay. Blue lines come from the relay. The results arrive best match first, not newest first.</p>190191 <div class="crt">192 <div class="titlebar">Session transcript - wss://search.example.com</div>193<pre><span class="c">-- check support first (NIP-11, over HTTP) --</span>194<span class="c">GET https://search.example.com =&gt; {"supported_nips": [1, 11, 50, ...], ...}</span>195196<span class="c">-- plain search, restricted to kind-1 notes --</span>197<span class="send">→ ["REQ", "find", {"kinds":[1], "search":"best nostr apps", "limit":3}]</span>198<span class="recv">← ["EVENT", "find", {"id":"7d1c02aa…", "kind":1,199 "content":"My list of the best nostr apps in 2026 …", …}]</span>200<span class="recv">← ["EVENT", "find", {"id":"e0937f21…", "kind":1,201 "content":"Which nostr apps do you rate best?", …}]</span>202<span class="recv">← ["EVENT", "find", {"id":"41bc55d0…", "kind":1,203 "content":"App roundup: notes, zaps, search …", …}]</span>204<span class="recv">← ["EOSE", "find"]</span>205<span class="send">→ ["CLOSE", "find"]</span>206207<span class="c">-- search with extensions: one domain, English, no spam filter --</span>208<span class="send">→ ["REQ", "find2", {"search":"best nostr apps domain:example.com language:en include:spam"}]</span>209<span class="recv">← ["EVENT", "find2", {"id":"9ac311be…", "pubkey":"22a12a12…", …}]</span>210<span class="recv">← ["EOSE", "find2"]</span></pre>211 </div>212213 <p>The client sent the second query with three extensions. A relay that supports none of them SHOULD run the search as if the query were only <code>best nostr apps</code>.</p>214215 <h2 class="bar" id="history"><span class="num">7</span> Version History</h2>216 <p>NIP-50 started as a "keywords" filter and became a free-text search frame within its first ten days. Notable changes, newest first:</p>217 <ul class="patchnotes">218 <li><span class="ver">2026</span><span class="when">2026-06</span>An <code>autocomplete:true/false</code> extension was added, then reverted three days later. It is not part of the spec.</li>219 <li><span class="ver">2025</span><span class="when">2025-12</span>Added the <code>relay</code> tag to the header: this NIP is a relay feature.</li>220 <li><span class="ver">2024</span><span class="when">2024-06</span>Defined the order of results: descending by match quality, with <code>limit</code> applied after scoring.</li>221 <li><span class="ver">2024</span><span class="when">2024-03</span>Added the classification extensions <code>language</code>, <code>sentiment</code>, and <code>nsfw</code>; added the <code>domain</code> extension for <a href="05.html">NIP-05</a> domains.</li>222 <li><span class="ver">2023</span><span class="when">2023-01</span>Rewrote the spec: <code>keywords</code> became the free-text <code>search</code> field, and the title became "Search Capability".</li>223 <li class="minor"><span class="ver">2023</span><span class="when">2023-01</span>Added the <code>supported_nips</code> discovery hint and the client-side filtering hint days after the origin.</li>224 <li><span class="ver">Origin</span><span class="when">2023-01-17</span>Artur Brugeman opened the NIP as "Keywords Filter".</li>225 </ul>226227 <div class="credits">228 <h3 class="cardhead">Original Proposal - Credits</h3>229 <table>230 <tr><td>Author</td><td>Artur Brugeman</td></tr>231 <tr><td>First commit</td><td>2023-01-17 — “NIP-50: Keywords Filter”</td></tr>232 <tr><td>Status</td><td><span class="chip draft">draft</span> <span class="chip">optional</span> — a relay feature; clients must expect relays without it</td></tr>233 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/50.md">50.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/50.md">full change history</a></td></tr>234 </table>235 </div>236237 <h2 class="bar"><span class="num">8</span> See Also</h2>238 <ul>239 <li><a href="01.html">NIP-01</a> — the filter object and the <code>REQ</code> message that <code>search</code> extends.</li>240 <li><a href="11.html">NIP-11</a> — the relay information document; its <code>supported_nips</code> field advertises search support.</li>241 <li><a href="05.html">NIP-05</a> — DNS-based identifiers; the <code>domain:</code> extension filters by a user's valid NIP-05 domain.</li>242 </ul>243 </div>244245 <aside>246 <div class="cartridge">247 <div class="inner">248 <h3 class="cardhead">Spec Card</h3>249 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>250 <dl>251 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span><span class="chip">relay</span></dd>252 <dt>Defines</dt><dd><code>search</code> filter field - key:value query extensions</dd>253 <dt>Kinds</dt><dd>none — applies to events of any kind</dd>254 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> filters - <a href="11.html">NIP-11</a> for discovery</dd>255 <dt>Transport</dt><dd>WebSocket - <code>REQ</code> messages</dd>256 <dt>First commit</dt><dd>2023-01-17</dd>257 <dt>Last updated</dt><dd>2026-06-13</dd>258 </dl>259 </div>260 </div>261 </aside>262 </main>263264 <nav class="navbtns">265 <a href="49.html">◀ NIP-49: Private Key Encryption (<code>ncryptsec</code>)</a>266 <a href="51.html">NIP-51: Lists ▶</a>267 </nav>268269 <footer class="pagefoot">270 <a href="../index.html">Nostr Protocol Manual</a>271 <span class="pageno">NIP 50 / F4</span>272 </footer>273</div>274275<script src="../assets/webmcp.js" defer></script>276</body>277</html>