Skip to content

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

TermMeaning
ShellThe trusted hosting web application that contains napplet iframes.
NappletA sandboxed iframe application that communicates via postMessage.
dTagThe napplet type identifier from its NIP-5A manifest.
Aggregate hashA SHA-256 over the napplet's build files; identifies an exact build.
NAPNostr 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 with iframeWindow.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:

ts
{ type: "<domain>.<action>", ...payload }

Request/response pairs are correlated by an id field:

ts
// 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 Window reference to the napplet's (dTag, aggregateHash) tuple, read from the NIP-5A manifest.
  • The shell MUST verify MessageEvent.source on every inbound message. MessageEvent.source is an unforgeable sender identity — origin validation uses source, not event.origin.
  • Messages from unmapped Window references 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 + aggregate x tag schema). It declares the capabilities it needs with requires tags: ["requires", "<nap-name>"].
  • The shell checks those requires tags 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 type strings within it, the payload shapes, and the expected shell behavior.
  • A NAP named foo owns all foo.* 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-origin would grant the napplet a real origin (letting it register a service worker, reach same-origin storage, etc.) — so it is prohibited.
  • MessageEvent.source provides 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:

For every normative requirement, read those — not this page. See NIP-5D spec status for how drift is tracked.

Released under the MIT License.