NIP-nip-21 Final
NIP-21: nostr: URI Scheme
URL scheme for linking to Nostr entities. Enable clickable links to profiles, events, and relays.
| Type | Nostr Implementation Possibility |
| Number | nip-21 |
| Status | Final |
| Original | https://github.com/nostr-protocol/nips/blob/master/21.md |
NIP-21: nostr: URI Scheme
Status: Final
NIP-21 defines the nostr: URI scheme for creating clickable links to Nostr entities. When users click these links, their Nostr client opens the appropriate content.
URI Format
nostr:<NIP-19 entity>
Supported Entities
| Type | Example |
|---|---|
| Profile | nostr:npub1... |
| Profile with relays | nostr:nprofile1... |
| Event | nostr:note1... |
| Event with context | nostr:nevent1... |
| Replaceable event | nostr:naddr1... |
Examples
Link to Profile
nostr:npub1qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqpzry23pqe
Link to Profile with Relay Hints
nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p
Link to Event
nostr:note1qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqpzryzmag7z
Link to Event with Relay Hints
nostr:nevent1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p
Creating Links
from nip19 import npub_encode, nprofile_encode, nevent_encode
def create_nostr_link(entity_type: str, data: dict) -> str:
"""Create a nostr: URI."""
if entity_type == "profile":
if data.get("relays"):
encoded = nprofile_encode(data["pubkey"], data["relays"])
else:
encoded = npub_encode(data["pubkey"])
elif entity_type == "event":
if data.get("relays"):
encoded = nevent_encode(
data["id"],
data.get("relays", []),
data.get("author"),
data.get("kind")
)
else:
encoded = note_encode(data["id"])
return f"nostr:{encoded}"
import { nip19 } from 'nostr-tools';
function createNostrLink(pubkey, relays = []) {
if (relays.length > 0) {
const nprofile = nip19.nprofileEncode({ pubkey, relays });
return `nostr:${nprofile}`;
}
const npub = nip19.npubEncode(pubkey);
return `nostr:${npub}`;
}
Parsing Links
import re
from nip19 import decode
def parse_nostr_uri(uri: str) -> dict | None:
"""Parse a nostr: URI."""
if not uri.startswith("nostr:"):
return None
entity = uri[6:] # Remove "nostr:" prefix
try:
decoded = decode(entity)
return decoded
except Exception:
return None
def extract_nostr_links(text: str) -> list:
"""Find all nostr: links in text."""
pattern = r'nostr:(n(?:pub|sec|ote|profile|event|addr)1[a-z0-9]+)'
matches = re.findall(pattern, text, re.IGNORECASE)
links = []
for match in matches:
parsed = parse_nostr_uri(f"nostr:{match}")
if parsed:
links.append(parsed)
return links
function extractNostrLinks(text) {
const pattern = /nostr:(n(?:pub|sec|ote|profile|event|addr)1[a-z0-9]+)/gi;
const matches = text.matchAll(pattern);
return Array.from(matches).map(m => {
try {
return nip19.decode(m[1]);
} catch {
return null;
}
}).filter(Boolean);
}
Rendering Links
In Content
When displaying content with nostr: links:
def render_nostr_links(content: str) -> str:
"""Replace nostr: URIs with clickable links."""
pattern = r'nostr:(n(?:pub|profile)1[a-z0-9]+)'
def replace(match):
uri = f"nostr:{match.group(1)}"
decoded = parse_nostr_uri(uri)
if decoded and decoded["type"] in ["npub", "nprofile"]:
# Link to profile
short_id = decoded["pubkey"][:8]
link = f'<a href="{uri}">@{short_id}...</a>'
return link
return match.group(0)
return re.sub(pattern, replace, content, flags=re.IGNORECASE)
Handling Clicks
// In a web client
document.addEventListener('click', async (e) => {
if (e.target.matches('a[href^="nostr:"]')) {
e.preventDefault();
const uri = e.target.getAttribute('href');
await handleNostrUri(uri);
}
});
async function handleNostrUri(uri) {
const decoded = nip19.decode(uri.slice(6));
switch (decoded.type) {
case 'npub':
case 'nprofile':
openProfile(decoded.data.pubkey || decoded.data);
break;
case 'note':
case 'nevent':
openEvent(decoded.data.id || decoded.data);
break;
case 'naddr':
openReplaceable(decoded.data);
break;
}
}
Use in Markdown
Nostr links work well in markdown content:
Check out [@alice](nostr:nprofile1...) latest post about Bitcoin!
I really liked [this thread](nostr:nevent1...).
Agent Usage
Generating Shareable Links
def share_my_profile() -> str:
"""Generate a shareable link to agent's profile."""
return create_nostr_link("profile", {
"pubkey": MY_PUBKEY,
"relays": MY_PREFERRED_RELAYS
})
def share_event(event_id: str) -> str:
"""Generate a shareable link to an event."""
return create_nostr_link("event", {
"id": event_id,
"relays": RELAYS,
"author": MY_PUBKEY
})
Processing Incoming Links
async def handle_mention_with_link(event: dict):
"""Process an event that mentions a nostr: link."""
links = extract_nostr_links(event["content"])
for link in links:
if link["type"] in ["npub", "nprofile"]:
# Someone linked a profile
pubkey = link.get("pubkey") or link.get("data")
profile = await fetch_profile(pubkey)
# Process profile...
elif link["type"] in ["note", "nevent"]:
# Someone linked an event
event_id = link.get("id") or link.get("data")
referenced = await fetch_event(event_id)
# Process event...
Machine-Readable Summary
{
"nip": 21,
"title": "nostr: URI Scheme",
"status": "final",
"defines": [
"uri-scheme",
"link-format"
],
"scheme": "nostr:",
"supported_entities": [
"npub",
"nprofile",
"note",
"nevent",
"naddr"
],
"related": [
"/learn/nostr/specs/nip-19",
"/learn/nostr/identifiers"
]
}