NIP-5D · Nostr Web Applets

# Composable Nostr apps, not monoliths.

A **Nostr Applet** — a **napplet** — is a small, sandboxed app that does one thing well. It runs inside a host **shell** and delegates signing, storage and relay access over a simple, typed message protocol — so apps stop rebuilding the same client, and every sensitive request is brokered by the shell.

[Get started](https://napplet.run/explainer/#start) [Read NIP-5D ↗](https://github.com/nostr-protocol/nips/pull/2303) [Join community ↗](https://armada.buzz/invite/naddr1qvzqqqyzz5pzpjk98hj7z978r9xc9d2ymagw6tga0lx0s06y8lhpy9twc2kp8uwdqqqqpwqpw5#BAACAwTDEKKhS9_iA_qOc1n4ljVt) [Documentation](https://napplet.run/docs/)

sandboxed & isolated by the host · every request via the shell · any shell, any napplet

**Web first — not web only** web napplets prove the protocol on the web by choice

-   A real, browser-enforced sandbox
-   The biggest developer ecosystem
-   Ships as a NIP-5A nsite — no servers

The napplet & [**NAP**](https://napplet.run/protocol/) model is transport-agnostic by design — room to grow beyond the browser.

Projections one capability seam, many hosts — see the [registry](https://napplet.run/protocol/)

| Projection | Binding | Status |
| --- | --- | --- |
| Web | `iframes + postMessage · window.napplet.*` | In use |
| Native | `OS process + IPC / FFI` | Possible |
| WASM | `host imports` | Possible |

wtf is an applet?

## applet

/ˈæp.lɪt/ noun

a small application built to do *one* narrow task — run *inside* a larger host program rather than launched on its own.

Blend *application* with the diminutive *\-let* and you get a tiny program made to live inside a bigger one. The word turns up in print [around 1990](https://www.merriam-webster.com/dictionary/applet), then gets burned into a generation’s memory by [Java applets](https://en.wikipedia.org/wiki/Java_applet) running sandboxed in the browser — though the instinct of shipping a little program to extend a remote machine goes all the way back to [RFC 5](https://www.rfc-editor.org/rfc/rfc5.html) in 1969. A **napplet** is that same idea, reborn Nostr-native.

The problem

## Nostr clients are monoliths

The protocol is composable. The clients are not. Each one is a giant app that owns your keys and reinvents the same building blocks — so good ideas stay trapped inside whichever client shipped them.

### Everyone rebuilds everything

Feeds, DMs, profiles, relay management, search — every client re-implements the same surface from scratch.

### Your key is everywhere

Each app you install asks for direct signing access. The more apps that hold that power, the larger your exposure.

### No real composition

You can’t drop one team’s great feed into another team’s great messenger. Features are welded to clients.

### All-or-nothing trust

Installing a client means trusting the whole thing — its code, its relays, its handling of your data.

The shift

## Invert the client

NIP-5D splits the monolith in two. A trusted **shell** brokers every sensitive request — signing, relays, storage. Focused **web napplets** render the UI and ask the shell to do that work. Same protocol — completely different shape.

Today

### One monolith, rebuilt every time

nostr-client

FeedDMsProfilesRelaysSigningStorageSearchZapsSettingsMediaNotifsLists

Signing, relays & UI all fused together. Every client re-implements the same surface — and wires up your signer and relays itself.

With napplets

### A shell hosts swappable napplets

shell

sandbox Feed

sandbox Chat

sandbox Profile

sandbox Relays

mediation signing relays storage

Each napplet does one thing, sandboxed. The shell brokers every sensitive call — to a signer, extension or relay. Swap napplets like apps.

How it works

## A message bus with a trust boundary

Every sensitive request goes through the shell, which brokers it to a signer, extension or relay. Web napplets just send envelopes and get answers back. On the web — the first target — that boundary is an `iframe` and `postMessage`.

sandboxed iframe napplet

`window.napplet.relay.subscribe()`

trusted host shell

identity via source ACL check NAP dispatch

nostr relays

shell mediates

01

### One envelope, every message

Napplet and shell speak a single JSON wire format: { type: "domain.action", ...payload } over postMessage. No SDK lock-in — it’s just messages.

02

### Identity bound to the document

The verified app hands the shell a private channel before any of its code runs. If the frame loads anything else, the shell cuts the channel and drops the app.

03

### Sandboxed by construction

Iframes use allow-scripts only — no allow-same-origin. Napplets can’t read shell storage, cookies or keys. Everything sensitive is proxied.

04

### Capabilities, negotiated

A runtime injects window.napplet.relay when relay access is available. NAP domains carve the protocol into modular capabilities.

Benefits

## Why the pattern wins

Splitting trust from UI isn’t just tidy — it changes what’s safe, what’s reusable, and who’s in control.

### Every request goes through the shell

Napplets never call a signer, relay or store directly. They ask the shell, which brokers each request — to a remote signer, an extension, or its own services — and can say no.

### Real sandbox isolation

allow-scripts without allow-same-origin: no service workers, no shell storage, no cross-origin reach. Each napplet does one thing, so its surface and permissions stay small.

### Compose, don’t rebuild

Drop a great feed next to a great messenger. Napplets are interchangeable parts, not features welded into one app.

### A bigger pool of builders

The hard Nostr protocol lives behind the shell. Developers can ship a useful napplet without deep protocol knowledge — and without putting users at risk.

### The user stays in control

The shell brokers every relay, storage and signing request, and scopes storage per napplet. Consent lives with the host, not the app.

### Portable across shells

Write to NIP-5D, run in any compatible shell. No vendor SDK lock-in — the protocol is the contract.

The paradigm shift

## An app store, not an app

Treat the shell like an operating system for Nostr. Napplets become installable, swappable programs — and the ecosystem stops being a handful of monoliths.

Clients compete on features Shells compete on trust & UX

When any napplet can run anywhere, a shell wins by being safer and nicer to use — not by hoarding features.

Pick one app, get its whole worldview Assemble your own client

Mix a feed from one team, a wallet from another, a niche tool from a solo dev. Your client becomes a composition.

Deep Nostr expertise to ship anything One dev ships one great napplet

The hard protocol lives behind the shell. Render UI, send envelopes — no relay pools, NIP-46 signers or event-signing to get right. A far broader pool of builders can contribute.

Trust the whole client with your signer Trust the shell, sandbox the rest

A single audited host brokers every sensitive request. Everything else runs contained, asking the shell for what it needs.

Runtimes & shells

## Any shell can be a runtime

A **runtime** is the host that actually runs your web napplet — it brokers your requests to a signer, extension or relay and talks to the network. On the web, a runtime is any app that honors **NIP-5D** — the security posture and transport for web napplets — and there can be many. That’s the point.

Your napplet UI + product logic — the one thing it does well

@napplet/shim · @napplet/sdk window.napplet global, or named exports for bundlers

NIP-5D envelope { type: "domain.action", ...payload } over postMessage

Shell / runtime Brokers every request, enforces ACL, talks to signers & relays

[Kehto — reference runtime ↗](https://github.com/kehto/web)

Nostr network Relays, signers, the wider protocol

For client developers

### Already built a Nostr client?

You’ve solved the hard parts — relay pools, signer flows, storage, key handling. Expose them through NAP interfaces and your client becomes a shell. Host third-party web napplets without rewriting your app, and turn your client into a platform.

-   Reuse your existing relay & signer plumbing
-   Host an ecosystem instead of shipping every feature
-   Compete on trust & UX, not feature count

For protocol developers

### Want to shape the protocol?

NIP-5D is small and the surface is well-defined. Stand up a runtime, implement the NAP capability domains, and help decide what the protocol becomes. Conformance work and new NAPs are where the design is still being written.

-   Implement NIP-5D — transport, identity, dispatch
-   Define & extend NAP capability domains
-   Drive conformance against the reference runtime

[Browse the NAPs track ↗](https://napplet.run/protocol/)

Reference runtime

### Kehto implements NIP-5D end-to-end

The reference shell where the protocol runs for real — host a napplet, watch the envelopes flow, and use it as the conformance target while you build a runtime of your own.

[Explore Kehto ↗](https://github.com/kehto/web) [How shells work](https://napplet.run/docs/)

Packages

## The napplet SDK

Small, ESM-only, zero-framework packages. Import the shim for a window global, the SDK for named exports, or pull a single NAP domain.

Alpha tooling

The development ecosystem is alpha. Packages can break, tooling paths may be incomplete, and the living spec can drift under active implementation.

[

`@napplet/core`

JSON envelope types and NAP dispatch infrastructure shared by the SDK packages. Protocol requirements live in NIP-5D and the NAP specs.

Docs · npm · JSR

](https://napplet.run/docs/packages/core.html)[

`@napplet/shim`

Runtime-side domain installers for the window.napplet namespace. Shells inject the available domains before napplet scripts run.

Docs · npm · JSR

](https://napplet.run/docs/packages/shim.html)[

`@napplet/sdk`

Named TypeScript exports wrapping window.napplet for bundler consumers — relay, inc, services, storage objects plus NAP message types.

Docs · npm · JSR

](https://napplet.run/docs/packages/sdk.html)[

`@napplet/nap`

All NAP domains as layered subpath exports — barrel, types-only, shim-only, or sdk-only. Tree-shakable. Import from a domain subpath.

Docs · npm · JSR

](https://napplet.run/docs/packages/nap.html)[

`@napplet/vite-plugin`

Build-time napplet manifest generation. Hashes each file and signs a kind 35129 manifest event (NIP-5A tag schema).

Docs · npm · JSR

](https://napplet.run/docs/packages/vite-plugin.html)[

`@napplet/cli`

Standalone CLI for creating projects, owning deploy metadata, previewing in Paja, and publishing NIP-5A manifests to Blossom and Nostr relays.

Docs · npm · JSR

](https://napplet.run/docs/packages/cli.html)[

`@napplet/conformance`

Framework-agnostic protocol conformance engine — reference mock shell, per-NAP envelope validators, a manifest validator, and reporters. A dev/test tool, not loaded in the sandbox.

Docs · npm · JSR

](https://napplet.run/docs/packages/conformance.html)[

`@napplet/conformance-cli`

The headless napplet-conformance runner. Drives the conformance engine against your napplet in real Chromium via Playwright — wire it up as test:conformance.

Docs · npm

](https://napplet.run/docs/packages/conformance-cli.html)[

`@napplet/conformance-web`

Browser conformance runtime. Loads a napplet URL, runs the conformance engine live in the page, and powers the deployed /conformance app.

Docs · npm

](https://napplet.run/docs/packages/conformance-web.html)[

`@napplet/boilerplate`

Project scaffold behind napplet create. Clones the maintained Vite + TypeScript starter without owning deployment metadata.

Docs · npm

](https://napplet.run/docs/packages/boilerplate.html)

Agent skills

Building with a coding agent? The `napplet-*` skills teach it the sandbox boundary, the SDK surface, and the applet UI contract. Install them with the open skills CLI:

```
npx skills add napplet/web
```

[Skill reference ↗](https://napplet.run/docs/guide/agent-skills.html)

Alpha software

### Before you install

napplet is alpha. The CLI and tooling could be broken or incomplete, and the living specification could drift as it evolves.
