Skip to content

Getting started ​

This page walks you from zero to a deployable napplet. The standalone napplet CLI provisions its own cached tooling and does not require host Node.js, npm, npx, or Deno, including for screenshots and conformance. You will need Node.js 20+ and a package manager to develop the generated Vite project or use the optional skills.sh CLI. To actually run a napplet you also need a host shell — see running in a shell at the end.

1. Install the CLI ​

bash
# macOS or Linux
curl -fsSL https://napplet.run/install.sh | sh
powershell
# Windows PowerShell
irm https://napplet.run/install.ps1 | iex

Each installer downloads the platform binary and verifies it against the release's SHA256SUMS before replacing napplet. The @napplet/cli page documents the JSR/Deno alternative.

Run napplet guide at any point for the complete command sequence and links to the documentation for each stage.

2. Create the project ​

bash
napplet create my-napplet
cd my-napplet

napplet create delegates to the maintained github.com/napplet/boilerplate template. The generator creates the project and derives only its package name; it does not own deployment name, title, or archetype metadata.

3. Initialize deployment metadata ​

bash
napplet init

The wizard writes .napplet/config.yaml with the named-manifest d-tag, title, optional description, queryless napplet:<archetype>/<intent> convention metadata, relays, and Blossom servers. Automation can provide the same values explicitly:

bash
napplet init --name my-napplet --title "My Napplet" \
  --archetype napplet:note/open \
  --relay wss://relay.example --server https://blossom.example

The CLI validates the documented napplet:<archetype>/<intent> shape and deploys the complete queryless convention identity.

At runtime, INC emit and intent invoke/open may accept napplet:<archetype>/<intent>?name=value developer input. Their bindings transpose query pairs to text payload fields before sending a queryless normalized identity. Subscriptions, manifest discovery, and handler routing do not parse queries and use exact equality.

4. Install agent skills ​

bash
npx skills add napplet/web

The open skills CLI detects the coding agents on your machine (Claude Code, Codex, Cursor, Gemini CLI, Copilot, and many more) and installs the napplet-* skills where each agent reads them. Add -g for a global install, --skill napplet-make for a single skill, or -a claude-code to target one agent. See Agent skills for what each skill does.

5. Build and verify ​

bash
pnpm install
# Ask your agent to build the napplet.
pnpm verify
pnpm test:conformance

6. Preview, then deploy ​

bash
napplet deploy --dry-run
napplet deploy

The dry run resolves the same config, files, metadata, and manifest events without uploading or publishing. Configure a local or remote signer before the live deploy; see @napplet/cli.

Choose the Note Drafts path that matches how you want to learn:

All three create a small Nostr note composer that autosaves through storage, reads the shell-user pubkey through identity, publishes through outbox, and runs in Kehto/Paja.

Retrofit an existing app manually ​

For a new napplet, prefer the generator above. It owns the package manager pin, Vite config, single-file build plumbing, scripts, conformance wiring, and starter layout. Use manual wiring only when you are adding napplet support to an existing app.

Install the napplet-side SDK:

bash
npm install @napplet/sdk

And the build-time Vite plugin as a dev dependency, which generates the signed NIP-5A manifest event:

bash
npm install -D @napplet/vite-plugin
ts
// vite.config.ts
import { defineConfig } from 'vite';
import { nip5aManifest } from '@napplet/vite-plugin';

export default defineConfig({
  plugins: [nip5aManifest({ nappletType: 'my-napplet', artifactMode: 'single-file' })],
});

Mirror the boilerplate scripts so verification stays consistent:

jsonc
{
  "scripts": {
    "build": "vite build",
    "type-check": "tsc --noEmit",
    "verify": "npm run type-check && npm run build",
    "test:conformance": "npm run build && napplet-conformance ./dist"
  }
}

Build your first napplet ​

NIP-5D runtimes inject window.napplet before your code runs. Napplet code can use the injected namespace directly:

ts
// main.ts
// Read kind 1 notes through the shell's outbox-aware routing
const { events } = await window.napplet.outbox.query(
  [{ kinds: [1], limit: 20 }],
  { timeoutMs: 3000 },
);
for (const result of events) console.log('Note:', result.event.content);

// Subscribe to live updates through the same outbox boundary const sub = window.napplet.outbox.subscribe([{ kinds: [1], limit: 20 }], {   timeoutMs: 3000, }); sub.on('event', (result) => console.log('New note:', result.event.content));

// Publish a note — the shell signs and fans it out; your napplet never sees the key
const published = await window.napplet.outbox.publish({
  kind: 1,
  content: 'Hello from my napplet!',
  tags: [],
  created_at: Math.floor(Date.now() / 1000),
});
if (!published.ok) throw new Error(published.error ?? 'publish failed');

Notice you never imported a signing library and never touched a relay socket. The runtime sends outbox.query, outbox.subscribe, and outbox.publish JSON envelopes; the shell does relay discovery, signing, fanout, and policy.

Runtime injection vs. SDK ​

Runtimes and napplets use different packages.

@napplet/shim@napplet/sdk
Import styleimport { installNappletGlobal } from '@napplet/shim'import { outbox, inc, intent } from '@napplet/sdk'
Who uses itHost runtime before napplet scripts executeNapplet application code
What it doesInjects selected window.napplet.<domain> objectsNamed, typed exports that wrap injected domains
Required?Required for runtimes that use these packagesOptional convenience for bundler users

The sdk is an ergonomic, typed wrapper. Its methods delegate to window.napplet.* at call time and throw a clear error if the runtime did not inject the namespace or requested domain.

Typical napplet code imports only the SDK:

ts
import { outbox, inc, intent, storage } from '@napplet/sdk';

If you're writing a vanilla napplet with no build step, you can skip the sdk and use the injected window.napplet.* namespace directly.

For cross-napplet intent, call intent.invoke(uri, options?) or intent.open(uri, options?) with a convention URI such as napplet:profile/open?pubkey=abc123. An ok: true result means the runtime accepted delivery responsibility; it does not mean the target has already handled anything. An ok: false result carries error. The target receives the delivery through intent.onDelivery(handler) as a separate push with a runtime-attested sender, independent of the source lifetime and without any NAP-INC dependency. This is non-normative orientation; the living NAP-INTENT, NAP-INC, and web projection documents are authoritative.

Running in a shell ​

A napplet only does something when hosted by a compatible shell — the trusted runtime that owns keys, relays, and storage and proxies your requests. The reference runtime is Kehto.

TIP

The reference shell/runtime is Kehto: github.com/kehto/web. You can also find napplet projects under the github.com/napplet organization. For questions and coordination, join the community group chat.

Next, read Core concepts to understand the envelope, NAPs, the sandbox model, identity, and storage scoping.

Terminal and automation options ​

napplet init and napplet deploy guide you through missing settings in a terminal. Deployment previews and results use readable sections, and network deployments show upload and relay progress. For automation, add -i / --non-interactive and supply values through flags or configuration. CI and redirected streams never trigger prompts. Use napplet deploy -i --json for machine-readable results and --no-color for plain display. See the CLI reference for all options.

Released under the MIT License.