How to Add a Custom Event Kind to the Buzz Nostr System

To add a custom event kind in Buzz, declare a pub const constant in buzz-core/src/kind.rs, register it in the ALL_KINDS slice, add compile-time range validation, and update any relevant helper predicates like is_command_kind or is_shared_gated_kind.

The Buzz system—the open-source Nostr implementation developed by Block—centralizes all event kind definitions in a single source-of-truth module. When you add a custom event kind to the Buzz codebase, you extend the protocol to support new data types while maintaining type safety through Rust's compile-time checks. This guide walks through the exact implementation steps using the current source code structure.

Understanding Event Kind Ranges in Buzz

Buzz follows the Nostr protocol specifications for event kind numbering, organizing custom kinds into three distinct ranges with different storage and replacement behaviors.

Replaceable Events (10000–19999)

Events in this range are replaceable, meaning newer events with the same kind and pubkey automatically overwrite older versions. Buzz uses these for profile-level metadata that should persist but update cleanly.

Parameterized Replaceable Events (30000–39999)

This is the most common range for Buzz-specific extensions. These events are keyed by the tuple (pubkey, kind, d_tag), allowing multiple distinct replaceable items per author. According to the source code, most custom Buzz kinds belong in this range, including widget definitions and workflow configurations.

Ephemeral Events (20000–29999)

Events in this range are ephemeral—relays must not store them. Buzz uses these for transient updates like presence indicators or real-time typing notifications.

Step-by-Step Implementation Guide

1. Declare the Constant in kind.rs

Open crates/buzz-core/src/kind.rs and add a pub const declaration with a descriptive name and numeric value. Follow the existing naming convention (prefix with KIND_) and include a doc comment explaining the purpose.

/// Widget definition – a parameterized replaceable, owner-authored configuration.
pub const KIND_WIDGET_DEF: u32 = 30180; // Fits 30000-39999 range

Place this declaration alongside other kind constants, typically around line 85-90 in the current source structure.

2. Register the Kind in ALL_KINDS

Append your constant to the ALL_KINDS slice. This slice serves as the master registry for iteration and duplicate detection across the codebase.

pub const ALL_KINDS: &[u32] = &[
    // … existing entries …
    KIND_WIDGET_DEF,
];

The ALL_KINDS slice appears near line 334 in kind.rs and is referenced by the test suite to validate kind uniqueness.

3. Add Compile-Time Validation

Add a compile-time assertion to verify the constant sits in the correct numeric range. This prevents accidental misclassification that could break relay storage semantics.

For parameterized replaceable kinds (30000–39999):

const _: () = assert!(is_parameterized_replaceable(KIND_WIDGET_DEF));

For standard replaceable kinds (10000–19999):

const _: () = assert!(is_replaceable(KIND_MY_PROFILE_DATA));

These assertions execute at compile time, guaranteeing range correctness before the code ever runs.

4. Update Helper Predicates (Optional)

If your kind requires special access control or transactional behavior, update the relevant helper functions in kind.rs:

  • is_command_kind – For kinds that trigger transactional commands in the relay (e.g., workflow triggers, approval grants).
  • is_shared_gated_kind – For kinds defaulting to author-only visibility unless explicitly shared via a ["shared","true"] tag.
  • is_relay_only_kind – For kinds that clients must never submit directly.

Example of adding to command kinds:

pub const fn is_command_kind(kind: u32) -> bool {
    matches!(
        kind,
        KIND_WORKFLOW_DEF
        | KIND_DM_OPEN
        | KIND_WORKFLOW_TRIGGER
        | KIND_WIDGET_DEF   // ← newly added
    )
}

5. Validate with Tests

Run the built-in test suite to verify your changes. The repository contains a specific test, tests::no_duplicate_kind_values, which checks ALL_KINDS for collisions.

cargo test --workspace

# Or using the justfile:

just test-unit

Adding your constant to ALL_KINDS automatically includes it in this validation, ensuring no duplicate values exist across the system.

6. Document the New Kind

Create a Markdown file under docs/nips/ describing the semantic meaning, required tags (such as the d tag for parameterized replaceable kinds), and access-control rules. This documentation keeps the protocol specification synchronized with the implementation.

Publishing Events of the New Kind

Once compiled, you can publish events using the Buzz CLI. The buzz event publish command accepts the raw kind number and automatically constructs a properly signed Nostr event.

buzz event publish \
    --kind 30180 \
    --content '{"title":"Cool Widget","description":"A demo widget"}' \
    --tag d=widget-123 \
    --tag p=$(buzz auth pubkey)

The CLI converts the --kind parameter to a Kind::Custom variant and signs the event using the private key configured in the BUZZ_PRIVATE_KEY environment variable.

Key Source Files and Architecture

Understanding where these components live helps navigate the codebase:

  • crates/buzz-core/src/kind.rs – Central registry containing all KIND_* constants, the ALL_KINDS slice, and helper predicates (is_command_kind, is_shared_gated_kind, etc.).
  • crates/buzz-cli/src/commands/event.rs – CLI implementation that builds and signs events, referencing constants from the core crate.
  • crates/buzz-relay/src/handlers/*.rs – Ingest and dispatch logic that consults helper predicates to enforce access control for incoming events.
  • docs/nips/ – Markdown documentation directory for protocol specifications.

Summary

  • Select the appropriate range (10000–19999 for replaceable, 30000–39999 for parameterized replaceable, 20000–29999 for ephemeral) based on storage requirements.
  • Declare a pub const in buzz-core/src/kind.rs with clear documentation and a descriptive name.
  • Register in ALL_KINDS to enable iteration and duplicate detection across the system.
  • Add compile-time assertions using is_parameterized_replaceable() or is_replaceable() to enforce range correctness.
  • Update predicates like is_command_kind() if the kind triggers special relay behavior or access controls.
  • Run cargo test to verify no duplicate kind values exist in the registry.
  • Document the kind in docs/nips/ to maintain protocol specifications.

Frequently Asked Questions

What range should I use for a new Buzz-specific event kind?

For most Buzz extensions, use the parameterized replaceable range (30000–39999), which allows multiple items per author distinguished by a d tag. Use 10000–19999 for single-instance replaceable data (like unified profile settings) and 20000–29999 only for transient, non-stored events like presence updates.

How does Buzz prevent duplicate event kind values?

The codebase maintains the ALL_KINDS slice in buzz-core/src/kind.rs containing every defined kind constant. The test suite includes tests::no_duplicate_kind_values, which iterates this slice to detect collisions at test time. Adding your constant to ALL_KINDS ensures it participates in this validation.

What is the difference between command kinds and shared-gated kinds?

Command kinds (checked via is_command_kind()) trigger transactional side effects in the relay, such as creating workflows or managing approvals. Shared-gated kinds (checked via is_shared_gated_kind()) enforce author-only access by default unless the event contains a ["shared","true"] tag, providing a privacy-first default with opt-in sharing.

Where should I document a new custom event kind in Buzz?

Create a new Markdown file in docs/nips/ (or update an existing NIP document) describing the kind's purpose, required tags, JSON schema expectations, and any access-control rules. This location serves as the protocol specification that developers and client implementers reference when interacting with your new event type.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →