NAP domain reference
A NAP (Nostr Applet Protocol) is one capability contract between a napplet and its runtime — what the runtime provides (relay access, storage, intents, …) and exactly how a napplet asks for it. On the web, NIP-5D binds each NAP to a message domain: a NAP named foo owns all foo.* JSON envelope messages, their payload shapes, and the expected shell behavior. NAP contracts are proposed and maintained in the NAPs track.
The protocol is modular by design. A NAP must be independently implementable, and shells may support any subset of NAPs. That's why napplets feature-gate with injected domain property presence before using a domain and degrade gracefully when it's absent — see Core concepts.
These domains ship as subpaths of @napplet/nap (barrel / types / shim / sdk per domain).
The domains
Each domain below shows its purpose and a minimal example. Examples assume the runtime injected window.napplet before napplet code ran; the same calls are also importable as named helpers from @napplet/sdk. Feature-gate optional domains with if (window.napplet?.domain) first.
relay
Low-level Nostr relay proxy — subscribe, publish, and one-shot query through the shell's relay pool. Use this only for explicit relay-local behavior such as a group relay, diagnostics, or protocol tooling. For normal social reads and publishes, use outbox or a higher-level domain such as common, lists, count, or dm.
// Explicit relay-local subscription (returns a handle with .close())
const sub = window.napplet.relay.subscribe(
[{ kinds: [9, 10, 11, 12], limit: 20 }],
(result) => render(result.event),
() => console.log('end of stored events'),
{ relay: 'wss://groups.example.com' },
);storage
Scoped key-value storage proxied through the shell — isolated per napplet identity.
await window.napplet.storage.setItem('draft', 'hello');
const draft = await window.napplet.storage.getItem('draft'); // string | null
const keys = await window.napplet.storage.keys();inc
Inter-napplet communication — topic-based publish/subscribe between napplets.
// One napplet broadcasts:
window.napplet.inc.emit('profile:open', [['p', pubkey]]);
// Another napplet listens:
const sub = window.napplet.inc.on('profile:open', (payload, event) => {
const target = event.tags.find((t) => t[0] === 'p')?.[1];
});keys
Keyboard bindings and action registration — the shell binds keys to named actions.
await window.napplet.keys.registerAction({ id: 'editor.save', label: 'Save', defaultKey: 'Ctrl+S' });
const sub = window.napplet.keys.onAction('editor.save', () => save());theme
Read-only shell theme access — colors, fonts, background, and title. The shell owns theming; the napplet reads it and reacts to changes.
const theme = await window.napplet.theme.get();
document.body.style.background = theme.colors.background;
const sub = window.napplet.theme.onChanged((t) => applyTheme(t));media
Ownership-aware media sessions and playback control.
const { sessionId } = await window.napplet.media.createSession({
owner: 'napplet',
metadata: { title: 'My Song', artist: 'The Artist' },
});
window.napplet.media.reportState(sessionId, { status: 'playing', position: 0, duration: 240 });
window.napplet.media.onCommand(sessionId, (action) => { if (action === 'pause') player.pause(); });notify
Shell-rendered notifications, badges, and interaction callbacks.
const { notificationId } = await window.napplet.notify.send({ title: 'New message', body: 'Alice: hey!' });
window.napplet.notify.badge(3);
window.napplet.notify.onAction((id, actionId) => { if (actionId === 'reply') openReply(id); });identity
Read-only user queries — pubkey, profile metadata, follows, … Never signs, encrypts, or decrypts.
const pubkey = await window.napplet.identity.getPublicKey();
const profile = await window.napplet.identity.getProfile();
const sub = window.napplet.identity.onChanged((pk) => reload(pk));config
Declarative per-napplet configuration (JSON Schema-driven). The shell renders the settings UI, validates, persists, and pushes live values; the shell is the sole writer.
const sub = window.napplet.config.subscribe((values) => applyTheme(values.theme as string));
window.napplet.config.openSettings({ section: 'appearance' });resource
Sandboxed byte fetching (info() -> ResourceInfo, bytes(url) -> Blob, bytesMany(urls) -> ResourceBytesItem[]) over https / blossom / nostr / data schemes — the only network-fetch primitive available inside the iframe sandbox.
const info = await window.napplet.resource.info();
const blob = await window.napplet.resource.bytes('https://example.com/avatar.png');
const items = await window.napplet.resource.bytesMany([
'https://example.com/avatar.png',
'blossom:sha256:abc123…',
]);
// Managed object URL — revoke when done to free memory
const { url, revoke } = window.napplet.resource.bytesAsObjectURL('blossom:sha256:abc123…');
imgEl.src = url;
imgEl.onload = () => revoke();cvm
Native ContextVM bridge — MCP-over-Nostr (discover / listTools / callTool / listResources / readResource / registry.*); the shell owns transport, registry selection, and tool policy.
if (window.napplet?.cvm) {
const servers = await window.napplet.cvm.discover({ search: 'relay' });
const tools = await window.napplet.cvm.listTools(servers[0]);
const result = await window.napplet.cvm.callTool(servers[0], tools[0].name, {});
}outbox
Outbox-aware relay routing — getEvent / query / subscribe / publish / resolveRelays; the shell owns NIP-65 relay discovery, fallback, dedup, signature validation, signing, and fanout. This is the default event-read and publish boundary when relay selection is part of result correctness.
if (window.napplet?.outbox) {
await window.napplet.outbox.getEvent('ev1…', { author: 'ab12…' });
const { events } = await window.napplet.outbox.query(
[{ authors: ['ab12…'], kinds: [1], limit: 20 }],
{ authors: ['ab12…'], timeoutMs: 3000 },
);
const sub = window.napplet.outbox.subscribe(
[{ authors: ['ab12…'], kinds: [1], limit: 20 }],
{ authors: ['ab12…'], timeoutMs: 3000 },
);
sub.on('event', (result) => render(result.event, result.sidecar?.relayHints));
await window.napplet.outbox.publish({
kind: 1,
content: 'gm',
tags: [],
created_at: Math.floor(Date.now() / 1000),
});
}upload
Shell-mediated file/blob upload over NIP-96 + Blossom rails; the shell signs auth and returns NIP-94 metadata.
if (window.napplet?.upload) {
const info = await window.napplet.upload.info();
const result = await window.napplet.upload.upload({ data: blob, filename: 'pic.png' });
if (result.status === 'complete') attach(result.url, result.nip94);
}intent
Archetype intent dispatch — invoke another napplet by role, with the shell resolving the default handler, window lifecycle, and trust boundary.
if (window.napplet?.intent) {
const { available } = await window.napplet.intent.available('note');
if (available) await window.napplet.intent.open('note', { target: { type: 'event', id } });
}ble
Runtime-mediated Bluetooth LE/GATT sessions. Napplets receive opaque session ids and byte arrays while the shell owns chooser UI, permissions, device handles, GATT lifecycle, notifications, disconnects, and policy.
if (window.napplet?.ble) {
const { session } = await window.napplet.ble.open({ acceptAllDevices: true });
const services = await window.napplet.ble.services(session.id);
}link
Shell-mediated external link opening. This is user-visible navigation, not byte fetching; the shell owns prompting, policy, opener isolation, and browser context.
if (window.napplet?.link) {
const result = await window.napplet.link.open('https://example.com/post/123', {
label: 'Read post',
});
if (result.status === 'denied') showInlineFallback();
}lists
Runtime-mediated NIP-51 list mutations. Napplets send add/remove intent while the runtime owns current-event lookup, kind/type mapping, tag formatting, private item encryption, event preservation, signing, and publishing.
if (window.napplet?.lists) {
await window.napplet.lists.add({ type: 'mute-list' }, [
{ itemType: 'pubkey', value: 'abc123...' },
]);
}common
Common social actions — public NIP-19 helpers, profile lookup, follows, follow/unfollow, reactions, and reports. The shell owns identity, consent, event construction, signing, publishing, relay access, and NIP-19 handling.
if (window.napplet?.common) {
const { pubkeys } = await window.napplet.common.follows();
await window.napplet.common.react(eventId, '+');
}Core domain union
@napplet/core exports a NapDomain string union for the foundational domains — relay, identity, storage, inc, theme, keys, media, notify, config, resource, cvm, outbox, upload, intent, ble, webrtc, link, lists, serial, common — used as the discriminant for envelope routing and domain presence.
Where to go next
@napplet/nap— the package and its subpath patterns@napplet/sdk— typed helpers and per-domain message unions- NIP-5D explained — the NAP framework