1 Overview
This NIP defines the nsite: a static website hosted from Blossom assets. Blossom is a file storage protocol. It stores files as blobs and addresses each blob by its sha256 hash.
An author uploads the site files to Blossom servers. Then the author publishes a site manifest event. The manifest maps each absolute path, such as /index.html, to the sha256 hash of the file at that path. A host server is a HTTP server that serves these pubkey static websites. It reads the manifest from relays, fetches the blobs from Blossom, and serves them to normal web browsers.
The site lives in the author's signature, not on any one server. Any host server can serve it. Any Blossom server can store the files. The spec also defines named sub-sites, immutable snapshots, a deterministic aggregate hash for each site version, and rules for copying a site into another author's namespace.
Think of the manifest as a tiny signed file-system table. The website itself never touches a relay. Relays carry only the map; Blossom carries the bytes.
2 Site Manifest Kinds
A site manifest event must be a regular, replaceable, or addressable event as defined in NIP-01. The spec uses one kind from each storage class:
| Kind | Storage class | Purpose |
|---|---|---|
| 15128 | replaceable | Root site. One per pubkey. Must not include a d tag. This is the pubkey's main site. |
| 35128 | addressable | Named site. A smaller site under the same pubkey; think of it as a subdomain. Must include a d tag with the site identifier. |
| 5128 | regular | Manifest snapshot. An immutable copy of a root or named site at one point in time. See §5. |
Named sites and the d tag
A named site uses kind 35128 and must include a d tag. The d tag value is the site identifier inside the author's namespace.
For canonical named-site URLs, the d value must match ^[a-z0-9-]{1,13}$ and must not end with -. The length limit comes from DNS: a DNS label holds at most 63 characters, and the base36 pubkey uses 50 of them. That leaves 1 to 13 characters for the identifier.
{
"kind": 35128,
"content": "",
"tags": [
["d", "blog"],
["path", "/index.html", "186ea5fd…ca1c99"],
["path", "/post.html", "a1b2c3d4…123456"],
["x", <site aggregate sha256>, "aggregate"]
],
…pubkey, created_at, id, sig as in NIP-01…
}
3 Manifest Tags
The manifest's content carries no site data; everything lives in the tags. The full tag set:
| Tag | Role | Required |
|---|---|---|
| path | Maps an absolute path to the sha256 hash of the file served at that path. | Yes, one or more |
| x | Carries the aggregate hash of the full manifest, so the site version is indexable and discoverable. | Optional, recommended (mandatory in snapshots, see §5) |
| d | Short identifier of a named site. Used only on kind 35128. | Named sites only |
| a | References the immediate parent nsite that this copied site was made from. | Copied sites only (and snapshots, see §5) |
| A | References the origin nsite of a copied site's lineage. | Copied sites only |
| server | Hints which Blossom servers can serve the blobs. | No |
| title | Human-readable site title. | No |
| description | Short human-readable description of the site. | No |
| source | Points to the site's source code repository or source archive. | No |
| app | References an upstream app descriptor event that the manifest is part of. See §6. | No |
Tag formats
- path — the event must include one or more
pathtags. Each has the exact format["path", "/absolute/path", "sha256hash"]. The first element is the literal string"path". The second element is an absolute path that ends with a filename and extension. The third element is the sha256 hash of the file served under that path. - x — the event should include one
xtag with the site aggregate hash (§4). Its format is["x", "<sha256-hex>", "aggregate"], where the hash is lowercase hexadecimal. - server — the event may include
servertags that hint which Blossom servers hold the blobs. - title / description — the event may include these tags for simple site information.
- source — the event may include one
sourcetag with the format["source", "<url>"]. For a source code repository, the URL may be anostr://git URL as defined by NIP-34, or an absolutehttps://git URL. For a source archive, the URL must be an absolutehttps://URL.
The source spec's own tag table is out of sync with its prose in three places. It omits the app tag, which the app-descriptor section defines. It marks x as optional, but snapshots (kind 5128) require exactly one. It marks a as "copied sites only", but snapshots also require exactly one a tag. The table above restores all three.
4 The Aggregate Hash
The aggregate hash is a deterministic hash of the site's path tags. It identifies one specific site version. Two site manifest events are equivalent if and only if they produce the same aggregate hash, even when different pubkeys publish them.
The hash must be computed from the path tags only. It must not depend on the order of tags in the manifest. It must ignore every other tag and every other event field, including app tags. Compute it like this:
- Collect every
pathtag. - For each tag, produce one line in the exact format
<sha256hash> <absolute-path>\n. - Sort all lines in ascending lexicographic order.
- Concatenate the sorted lines as UTF-8 bytes.
- Compute the SHA-256 hash of the bytes.
Encode the digest as lowercase hexadecimal. Put it in the manifest as ["x", "<sha256-hex>", "aggregate"] so it is indexable for discovery and lookup. Root and named sites should carry this tag. Snapshot events of kind 5128 must carry exactly one, and it must exactly match the parent nsite's aggregate x tag.
186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99 /index.html fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321 /favicon.ico
# shell, using nak + jq nak req -k 15128 -a <pubkey> --limit 1 wss://relay.example.com \ | jq -r '.tags[] | select(.[0] == "path") | "\(.[2]) \(.[1])"' \ | sort | sha256sum // JavaScript const aggregateHash = sha256( event.tags.filter(t => t[0] === "path") .map(([, path, hash]) => `${hash} ${path}\n`) .sort().join(""))
5 Manifest Snapshots
A manifest snapshot uses kind 5128 and is a regular event. It captures the state of a root site or named site at one point in time. Root and named sites are replaceable, so old versions vanish; a snapshot keeps a historical version addressable by its own event id. Authors may publish snapshots alongside site updates to preserve version history.
A snapshot of an existing root or named site follows these rules:
- It must copy the source manifest's
pathtags. - It must include exactly one
xtag whose value exactly matches the source manifest's aggregatextag. - It must include exactly one
atag that references the source root site or named site. - If the source manifest includes an
Atag, the snapshot must copy thatAtag unchanged. If the source has noAtag, the snapshot should omit it. - Other valid nsite tags —
title,description,source,app,server, relay hints — may be copied if the author wants to preserve them. They are optional.
The a tag identifies which site the snapshot captures. The snapshot's own created_at is the version timestamp of that snapshot.
{
"kind": 5128,
"pubkey": "266815e0…cd408a5",
"created_at": 1727373475, // the version timestamp
"content": "",
"tags": [
["a", "35128:266815e0…cd408a5:blog"], // the snapshotted site
["path", "/index.html", "186ea5fd…ca1c99"],
["path", "/post.html", "a1b2c3d4…123456"],
["x", <site aggregate sha256>, "aggregate"],
["server", "https://blossom.example.com"],
["title", "My Blog v1"],
["description", "An immutable snapshot of a blog hosted on Nostr"],
["source", "https://github.com/example/my-nostr-blog"]
]
}
6 Upstream App Descriptors
An nsite may include one or more app tags. Each references an upstream app descriptor event that the manifest is part of. The format is ["app", "<kind>:<pubkey>:<d-tag>", "<relay>"]. The second element is an addressable event reference to the app descriptor. The third element is a relay hint for locating that event.
This linkage is optional. It makes room for application-level discovery and packaging flows without extending the nsite manifest itself. The descriptor kind is intentionally generic: an nsite can reference NIP-89 applications and other present or future descriptor forms. App store metadata, discoverability, and runtime compatibility belong in those descriptor events. The nsite manifest stays focused on describing the site's files.
7 Copying nsites
An nsite may be created by copying another root site or named site. The copy pins that site under a different author's namespace at a specific state. The new author then maintains it independently. Authors who copy a site should copy all tags from the source whenever they remain applicable: path, x, app, server, title, description, and source.
A copy does not have to keep the original kind or identifier. A copied root site may become a named site. A copied named site may become a root site. A copied named site may use a different d value than its parent. Because kind and identifier can change, the lineage tags below are the stable, indexable references:
- A copied site must include exactly one lowercase
atag that references the immediate parent nsite it was copied from. - A copied site must include exactly one uppercase
Atag that references the origin nsite of the copy lineage. - For a direct copy of an original site,
aandAmay reference the same nsite. - For a copy of a copied site, the
Atag must be copied unchanged from the parent site. - Sites that were not copied should not include
aorAtags.
a tag names the immediate parent. The A tag always names the origin site, copied unchanged down the chain.{
"kind": 35128,
"pubkey": "7a6815e0…cd4bbbb", // Carol
"content": "",
"tags": [
["d", "blog"],
// immediate parent: Bob's copied site, not the origin
["a", "35128:9f6815e0…cd4000ff:blog"],
// origin of the lineage: Alice's site, copied unchanged from Bob
["A", "35128:266815e0…cd408a5:blog"],
["path", "/index.html", "186ea5fd…ca1c99"],
["path", "/post.html", "c1b2c3d4…123456"],
["x", <site aggregate sha256>, "aggregate"],
["title", "My Derived Blog"]
]
}
A snapshot may also capture a copied site. The snapshot's a tag then points at the copied site being captured, and the copied site's A tag is carried over unchanged, so the origin lineage survives in the snapshot.
8 Host Server Behavior
A host server is a HTTP server responsible for serving pubkey static websites. This section defines how it maps hostnames to manifests and paths to blobs.
Address formats
For interoperability, host servers should use these canonical URL formats. Each site fits in a single DNS label, which avoids wildcard-certificate limitations with multi-level subdomains.
| Site type | Label format | Example |
|---|---|---|
| Root site | <npub>.nsite-host.com | npub10phxfsms72rhafrklqdyhempujs9h67nye0p67qe424dyvcx0dkqgvap0e.nsite-host.com |
| Snapshot | v<snapshotIdB36>.nsite-host.com | v<50-char-snapshotIdB36>.nsite-host.com |
| Named site | <pubkeyB36><dTag>.nsite-host.com | <50-char-pubkeyB36><dTag>.nsite-host.com |
pubkeyB36is the author's raw 32-byte pubkey in base36: lowercase, digits0-9then lettersa-z, no padding. It is always exactly 50 characters.snapshotIdB36is the raw 32-byte snapshot event id in the same base36 encoding, also always exactly 50 characters.dTagis thedtag value as plain text, appended directly afterpubkeyB36with no separator.
A host server that uses subdomain routing may serve anything at its own root domain, nsite-host.com — a landing page, for example.
Parsing the label
For canonical subdomain formats, the host server must parse the left-most DNS label in this order:
- If the label is a valid
npub(NIP-19), decode it and resolve the root site manifest (kind15128). - Otherwise, if the label matches
^v[0-9a-z]{50}$, treat it as a snapshot label. The final 50 characters after the leadingvaresnapshotIdB36. Decode them to the 32-byte event id. Resolve the kind5128snapshot with that id. - Otherwise, if the label matches
^[0-9a-z]{50}[a-z0-9-]{1,13}$and does not end with-, treat it as a named-site label. The first 50 characters arepubkeyB36; decode them to a 32-byte pubkey. The remaining 1-13 characters are thedTag. Resolve the kind35128manifest for that pubkey and identifier. - If parsing fails, the host server must treat the site as not found.
Resolving paths
Once the host server finds the manifest event, it must extract the path-to-hash mappings from the path tags. It should look for a path tag whose second element matches the requested path. When it finds the match, it uses the sha256 hash in the third element to retrieve the file.
Index pages. If the request path does not end with a filename, the host server must fall back to the index.html filename. For example, / becomes /index.html and /blog/ becomes /blog/index.html.
Retrieving blobs
- The host server should prioritize the manifest's
servertags as hints. If the manifest hasservertags, try those Blossom servers first. - If the pubkey has a kind
10063BUD-03 user servers event, the host server must attempt to retrieve the file from the listed servers, using theGET /<sha256>path defined in BUD-01. - If the pubkey has no
10063event and the manifest has noservertags, the host server must respond with status code404.
The host server should verify that the sha256 hash of any served blob matches the hash in the corresponding path tag. If the hash does not match, the host server should treat the site as not found.
The host server must forward the Content-Type and Content-Length headers from the Blossom server. If neither is defined, it may set Content-Type from the file extension in the requested path.
Not found
If the host server cannot find a site manifest event, or cannot find a matching path tag for the requested path, it must use /404.html as a fallback path.
Serve only bytes whose sha256 matches the manifest. A Blossom server can return anything; the hash check is the only thing that binds the response to the author's signature.
9 Worked Example
Alice publishes her root site. The manifest below is the complete event she signs and sends to her relays. The three files were uploaded to blossom.example.com first.
{
"content": "",
"created_at": 1727373475,
"id": "5324d695ed7abf7cdd2a48deb881c93b7f4e43de702989bbfb55a1b97b35a3de",
"kind": 15128,
"pubkey": "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
"sig": "f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094c…",
"tags": [
["path", "/index.html",
"186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"],
["path", "/about.html",
"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"],
["path", "/favicon.ico",
"fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"],
["x", <site aggregate sha256>, "aggregate"],
["app", "31990:266815e0…cd408a5:my-app", "wss://relay.example.com"],
["app", "32267:266815e0…cd408a5:com.example.webapp", "wss://relay.example.com"],
["server", "https://blossom.example.com"],
["title", "My Nostr Site"],
["description", "A static website hosted on Nostr"],
["source", "https://github.com/example/my-nostr-site"]
]
}
A visitor opens https://npub10phxfsms72rha….nsite-host.com/about.html. The host server resolves the request like this:
-- browser to host server -- → GET /about.html Host: npub10phxfsms72rha….nsite-host.com -- label is a valid npub: decode → pubkey 266815e0…cd408a5 -- -- host server to relay -- → ["REQ", "site", {"kinds":[15128], "authors":["266815e0…cd408a5"], "limit":1}] ← ["EVENT", "site", {"kind":15128, "tags":[["path","/about.html","a1b2c3d4…"], …]}] ← ["EOSE", "site"] -- host server to blossom (server tag hint tried first) -- → GET https://blossom.example.com/a1b2c3d4e5f6…ef123456 ← 200 OK Content-Type: text/html Content-Length: 1832 -- verify sha256(body) == a1b2c3d4e5f6…ef123456 : match -- -- host server to browser -- ← 200 OK Content-Type: text/html Content-Length: 1832 (forwarded) -- a request for /missing.png finds no path tag -- → GET /missing.png ← 200 OK (body of /404.html, the mandatory fallback path)
10 Version History
NIP-5A is young and moves fast. Notable changes, newest first:
- 20262026-06Added the aggregate hash, the
xtag, and manifest snapshots (kind5128). - 20262026-06Typo fixes and code-fence formatting cleanups.
- 20262026-04Renamed the source file to
5A.mdfor clarity. - Origin2026-03hzrd149 published NIP-5A: root sites, named sites, copying, and the host server rules.
Original Proposal - Credits
| Author | hzrd149 |
| First commit | 2026-03-25 — “NIP-5A: Static Websites (#1538)” - PR #1538 |
| Status | draft - optional |
| Source | 5A.md on GitHub - full change history |
11 See Also
- NIP-01 — regular, replaceable, and addressable events; the storage classes the three manifest kinds use.
- NIP-19 — the
npubencoding parsed from root-site subdomain labels. - NIP-34 —
nostr://git URLs accepted in thesourcetag. - NIP-89 — app handler events, one target of the
appdescriptor tag. - Blossom (BUD-01, BUD-03) — blob storage,
GET /<sha256>, and the kind10063user server list.