NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub1m2jphmdkskg...

Blossom Folder Lists

Published Oct 4, 2025
kind 30000

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 d tag 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 d tag private holding 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

  • content may mirror the human-readable folder name.
  • If both ["name", ...] and content are 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

  • content must 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

  • version must be 1.
  • Every entry must include sha256. Clients ignore entries with missing sha256.
  • encryption is optional; when present, include algorithm, key, and iv.
  • metadata is optional; folderPath should follow the same normalization rules. Use null to clear a folder assignment.
  • servers is optional; Clients should trim each URL of trailing slashes.
  • updatedAt is optional but recommended for conflict resolution.
  • Clients publishing private lists overwrite the entire entries array; partial updates are undefined.

Path Normalization

When converting user input to folder paths (src/utils/blobMetadataStore.ts:520–550):

  1. Trim leading/trailing whitespace.
  2. Split on /, trim each segment, discard empty segments.
  3. Reject if any segment, after lowercasing and removing non-alphanumerics, contains the reserved keyword private.
  4. Join remaining segments with /. An empty result denotes the root.
  5. Inputs that normalize to null or undefined should 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_at value 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