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

> Learn how to add a custom event kind to the Buzz Nostr system. Discover the steps to declare constants, register kinds, and update validation for seamless integration.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: how-to-guide
- Published: 2026-08-29

---

**To add a custom event kind in Buzz, declare a `pub const` constant in [`buzz-core/src/kind.rs`](https://github.com/block/buzz/blob/main/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`](https://github.com/block/buzz/blob/main/kind.rs)

Open [`crates/buzz-core/src/kind.rs`](https://github.com/block/buzz/blob/main/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.

```rust
/// 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.

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

```

The `ALL_KINDS` slice appears near line 334 in [`kind.rs`](https://github.com/block/buzz/blob/main/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):

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

```

For standard replaceable kinds (10000–19999):

```rust
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`](https://github.com/block/buzz/blob/main/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:

```rust
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.

```bash
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.

```bash
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`](https://github.com/block/buzz/blob/main/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`](https://github.com/block/buzz/blob/main/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`](https://github.com/block/buzz/blob/main/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`](https://github.com/block/buzz/blob/main/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.