30817:blossom-folder-lists
Blossom Folder Lists
- signed by
- npub1m2jph...qr3wgu
- published
- 2025-10-04
- event
- 7b5839f4ad...eecc
Summary
This NIP standardizes how clients publish and consume parameterized, replaceable Nostr events that model file-system-style folders for blob storage backends (e.g., Blossom, NIP-96, Satellite). Each event represents a folder and lists the blob hashes it contains, allowing multiple clients to share identical folder structures.
Motivation
Blossom and related services expose flat blob namespaces. Bloom and similar applications need a portable way to represent folders without changing storage protocols. A shared event format lets clients:
- Synchronize folder structures across devices.
- Coordinate folder names and memberships.
- Extend storage tooling without proprietary metadata.
Terminology
- Folder List Event – kind 30000 parameterized replaceable event that declares one folder and enumerates blob hashes (
src/lib/folderList.ts:6). - Folder Identifier – the
dtag value that uniquely identifies a folder event (src/lib/folderList.ts:25). - Normalized Folder Path – slash-delimited path produced by
normalizeFolderPathInput(src/utils/blobMetadataStore.ts:539). - Blob Hash – lowercase SHA-256 hex digest referencing a stored object.
- Private Folder List – encrypted kind 30000 event under the
dtagprivateholding user-only metadata (src/lib/privateList.ts:5).
Public Folder Lists
Event Kind
Clients must publish folder definitions as kind 30000 parameterized replaceable events (per NIP-33 / NIP-51 semantics).
Identifier (d tag)
Every folder event must include:
["d", "<identifier>"]
<identifier> must be bloom-folder:<path>, where <path> is:
__root__for the root folder.- Otherwise
encodeURIComponent(<normalized-path>).
Relays and clients must treat identifiers as case-sensitive.
Folder Path Tag
["folder", "<normalized-path>"]
<normalized-path>must follow the normalization rules below.- Root folders use an empty string.
Display Name
Optional tag:
["name", "<display-name>"]
If absent, consumers should fall back to event.content when non-empty (src/lib/folderList.ts:80).
Blob Membership
For each blob, add:
["x", "<sha256>"]
<sha256>must be 64 lowercase hex characters.- Duplicate hashes in one event should be ignored.
Event Content
contentmay mirror the human-readable folder name.- If both
["name", ...]andcontentare present, the tag takes precedence.
Additional Tags
- Implementations may add extra tags.
- Consumers must ignore unknown tags unless otherwise agreed.
Private Folder Lists (Encrypted)
Bloom keeps a user-only catalog of blob metadata and folder assignments.
Event Kind & Identifier
- Still kind 30000.
- Use the parameterized identifier:
["d", "private"]
Encryption
contentmust be encrypted to the author with NIP-44, falling back to NIP-04 when NIP-44 is unavailable (src/lib/privateList.ts:144,src/lib/privateList.ts:191).- Consumers must decrypt with the signer's keypair.
Payload Structure
The decrypted JSON must follow this schema:
{
"version": 1,
"entries": [
{
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"encryption": {
"algorithm": "aes-256-gcm",
"key": "base64-key",
"iv": "base64-iv"
},
"metadata": {
"name": "Vacation Photo.jpg",
"type": "image/jpeg",
"size": 2048000,
"audio": null,
"folderPath": "photos/trips/2025"
},
"servers": ["https://blossom.example", "https://files.other"],
"updatedAt": 1719945600
}
]
}
Structure Rules
versionmust be 1.- Every entry must include
sha256. Clients ignore entries with missingsha256. encryptionis optional; when present, includealgorithm,key, andiv.metadatais optional;folderPathshould follow the same normalization rules. Usenullto clear a folder assignment.serversis optional; Clients should trim each URL of trailing slashes.updatedAtis optional but recommended for conflict resolution.- Clients publishing private lists overwrite the entire
entriesarray; partial updates are undefined.
Path Normalization
When converting user input to folder paths (src/utils/blobMetadataStore.ts:520–550):
- Trim leading/trailing whitespace.
- Split on
/, trim each segment, discard empty segments. - Reject if any segment, after lowercasing and removing non-alphanumerics, contains the reserved keyword
private. - Join remaining segments with
/. An empty result denotes the root. - Inputs that normalize to
nullorundefinedshould be rejected.
Root Folder
Represent the root folder with:
["d", "bloom-folder:__root__"]
["folder", ""]
- Clients should not store blob hashes in the root event; absence of hashes implies "unfiled" blobs.
Client Behavior
- Keep only the most recent event per identifier (the one with the highest
created_atvalue wins). - When applying folder membership, replace prior state with the latest event (
src/lib/folderList.ts:97). - Remove a blob by publishing a new event without that blob's
["x", ...]tag. - Delete a folder by publishing the identifier with no
["x", ...]tags and an empty display name. - For private lists, refresh and overwrite the complete entry set (
src/lib/privateList.ts:134).
Relay Recommendations
- Support parameterized replaceable semantics keyed by
(kind, pubkey, d). - Optionally enforce size limits to prevent oversized folder events.
Security Considerations
- Public folder lists expose mappings from pubkeys to blob hashes; treat as public metadata.
- Avoid placing sensitive information in folder names—blobs may reside on third-party servers.
- For private folder lists, implementations must safeguard decrypted payloads (e.g., never log plaintext).
Examples
Example Public Folder Event (JSON Template)
{
"kind": 30000,
"created_at": 1719945600,
"pubkey": "b6d1…",
"tags": [
["d", "bloom-folder:photos%2Ftrips%2F2025"],
["folder", "photos/trips/2025"],
["name", "Trips 2025"],
["x", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
["x", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
],
"content": "Trips 2025"
}
Example Root Folder Event
{
"kind": 30000,
"created_at": 1719945601,
"pubkey": "b6d1…",
"tags": [
["d", "bloom-folder:__root__"],
["folder", ""]
],
"content": " "
}
Example Private Folder List Event (Encrypted Template)
{
"kind": 30000,
"created_at": 1719945602,
"pubkey": "b6d1…",
"tags": [
["d", "private"]
],
"content": "<encrypted nip44 ciphertext>"
}
The decrypted payload utilizes the schema shown above.
Reference Implementation
- Public folder events:
src/lib/folderList.ts - Path normalization helpers:
src/utils/blobMetadataStore.ts:520–550 - Private folder list serialization:
src/lib/privateList.ts
Discussion
Connect a key to comment.