NIP-5D explained
NIP-5D — Nostr Web Applets is the specification that defines how a napplet and its host shell talk to each other. This page summarizes the model the @napplet/* packages implement.
NON-NORMATIVE — read the spec
This page is an orientation, not the specification. The living, authoritative documents are NIP-5D (PR #2303) and the NAPs track. For every normative requirement ("MUST", message shapes, manifest fields), defer to those — not to this page, the packages, or any test.
Philosophy
- Napplets are small and single-purpose rather than monolithic — a chat widget, a feed viewer, a profile editor, a relay manager are each separate applications.
- The shell composes multiple napplets; napplets do not compose themselves.
- The protocol is intentionally minimal: a JSON envelope over
postMessage, with capabilities layered on top through an extension framework.
Terminology
| Term | Meaning |
|---|---|
| Shell | The trusted hosting web application that contains napplet iframes. |
| Napplet | A sandboxed iframe application that communicates via postMessage. |
| dTag | The napplet type identifier from its NIP-5A manifest. |
| Aggregate hash | A SHA-256 over the napplet's build files; identifies an exact build. |
| NAP | Nostr Applet Protocol — one capability contract between a napplet and its runtime, defining the protocol messages for a capability domain. |
In this SDK, each NAP is a domain that owns one message domain (
relay,storage,identity, …). NAP contracts live in the NAPs track.
Transport
- Napplets send messages with
window.parent.postMessage(msg, '*'); the shell replies withiframeWindow.postMessage(msg, '*'). - Napplets MUST be embedded with
sandbox="allow-scripts"and withoutallow-same-origin. - Napplets have no access to
localStorage,sessionStorage,IndexedDB, direct WebSocket connections, or signing keys. - Shells MUST not provide
window.nostr(NIP-07). Signing and encryption are brokered by the shell instead — to a remote signer, an extension, or the shell's own key management, depending on the shell.
Wire format
Every message is a JSON object with a type field in domain.action format:
{ type: "<domain>.<action>", ...payload }Request/response pairs are correlated by an id field:
// request
{ type: "outbox.query", id: "abc", filters: [{ kinds: [1] }] }
// a result arrives back with a matching id
{ type: "outbox.query.result", id: "abc", events: [{ event: { /* NostrEvent */ } }] }The type prefix before the first . is the domain, and routes the message to the correct NAP handler. Unrecognized message types are silently ignored for forward compatibility — a napplet can speak to an older shell and degrade gracefully.
Identity
- The shell assigns a napplet's identity at iframe creation time, with no negotiation or handshake.
- It maps the iframe's
Windowreference to the napplet's(dTag, aggregateHash)tuple, read from the NIP-5A manifest. - The shell MUST verify
MessageEvent.sourceon every inbound message.MessageEvent.sourceis an unforgeable sender identity — origin validation usessource, notevent.origin. - Messages from unmapped
Windowreferences are silently dropped.
Manifest and NAP negotiation
- A napplet's manifest is a NIP-5D kind 35129 named-napplet event (adopting the NIP-5A
path+ aggregatextag schema). It declares the capabilities it needs withrequirestags:["requires", "<nap-name>"]. - The shell checks those
requirestags against its own capabilities at load time and can warn on a mismatch. - At runtime, a napplet detects support from domain object presence:
if (window.napplet?.relay) { ... }. - Napplets MUST gracefully degrade when a capability is absent.
The @napplet/vite-plugin generates this manifest at build time — computing per-file SHA-256 hashes, the aggregate hash, and the requires / connect / config tags.
NAP extension framework
- A NAP spec defines a message domain, the valid
typestrings within it, the payload shapes, and the expected shell behavior. - A NAP named
fooowns allfoo.*message types. - Each NAP must be independently implementable, and shells may support any subset of NAPs.
This is what makes the protocol modular: NAP contracts live in the NAPs track; see the NAP domain reference for the domains this SDK ships.
Security model
- Napplets are untrusted; the shell is trusted; the browser enforces the iframe sandbox boundary.
- Adding
allow-same-originwould grant the napplet a real origin (letting it register a service worker, reach same-origin storage, etc.) — so it is prohibited. MessageEvent.sourceprovides the unforgeable sender identity used for origin validation.- Shells MUST not sign or broadcast events containing ciphertext received from a napplet.
- The protocol does not protect against compromised browsers, malicious shells, or social engineering — it secures the napplet-shell boundary, not the shell itself.
Source of truth
This page is a non-normative summary. The living, authoritative documents are:
- NIP-5D (the protocol): github.com/nostr-protocol/nips/pull/2303
- NAPs track (the capability domains): github.com/napplet/naps
For every normative requirement, read those — not this page. See NIP-5D spec status for how drift is tracked.