| name | nostr-addressable-event-d-tag-requirement |
| description | Fix duplicate Nostr events (Kind 30000+ parameterized replaceable events) caused by missing d-tag.
Use when: (1) Same content appears multiple times on relay for same pubkey, (2) Events aren't
being replaced/updated as expected, (3) Publishing NIP-71 video events (Kind 34236) or other
addressable events and seeing duplicates. The d-tag is REQUIRED for addressability - without it,
each publish creates a new non-replaceable event.
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2026-01-29T00:00:00.000Z" |
Nostr Addressable Event d-tag Requirement
Problem
When publishing parameterized replaceable events (Kind 30000-39999, including NIP-71 video
events Kind 34236), events appear as duplicates on the relay instead of replacing each other.
The same content shows up multiple times for the same user.
Context / Trigger Conditions
- Publishing Kind 30000+ events (parameterized replaceable events)
- Same video/content appears multiple times in user's feed
- Events have identical pubkey, kind, and content but different event IDs
- Relay returns multiple events when you expect one
- Using NIP-71 (Kind 34236) for video events
- Using NIP-23 (Kind 30023) for long-form content
- Any addressable event type showing duplicates
Solution
Root Cause
Parameterized replaceable events (Kind 30000-39999) require a d tag to be addressable.
The combination of pubkey + kind + d-tag value forms the unique address. Without a d tag,
the relay treats each event as a separate non-replaceable event.
Fix
Always include a d tag with a unique identifier for the content:
const event = {
kind: 34236,
pubkey: userPubkey,
content: "Video description",
tags: [
["d", videoId],
["title", "My Video"],
["url", "https://..."],
]
};
const badEvent = {
kind: 34236,
pubkey: userPubkey,
content: "Video description",
tags: [
["title", "My Video"],
["url", "https://..."],
]
};
For NIP-71 Video Events
Use the video's unique identifier (vine_id, video_id, etc.) as the d-tag value:
tags.push(["d", video.id]);
Cleaning Up Existing Duplicates
- Fetch all events for the pubkey:
{ kinds: [34236], authors: [pubkey] }
- Identify events without d-tags (these are the duplicates)
- Publish NIP-09 deletion events (Kind 5) referencing the bad event IDs
- Republish with proper d-tags
Verification
After adding the d-tag:
- Publish the same content twice
- Query the relay for events with that pubkey+kind
- Should return only ONE event (the latest)
- Event ID will change but d-tag address remains constant
const events = await ndk.fetchEvents({ kinds: [34236], authors: [pubkey] });
const byDTag = new Map();
for (const e of events) {
const d = e.tags.find(t => t[0] === 'd')?.[1];
if (!byDTag.has(d)) byDTag.set(d, []);
byDTag.get(d).push(e);
}
for (const [d, evts] of byDTag) {
if (evts.length > 1) console.log(`Duplicate: d=${d} has ${evts.length} events`);
}
Example
Before fix - events without d-tag create duplicates:
Event 1: id=abc123, kind=34236, tags=[["title", "My Video"]]
Event 2: id=def456, kind=34236, tags=[["title", "My Video"]] // Duplicate!
After fix - events with d-tag replace each other:
Event 1: id=abc123, kind=34236, tags=[["d", "video-001"], ["title", "My Video"]]
// Republishing same content...
Event 2: id=xyz789, kind=34236, tags=[["d", "video-001"], ["title", "My Video Updated"]]
// Event 1 is replaced, only Event 2 exists
Notes
- The d-tag value should be stable across republishes (use content ID, not random)
- Empty string
["d", ""] is valid but means only one event of that kind per pubkey
- Regular replaceable events (Kind 0, 3, 10000-19999) don't need d-tag
- This applies to ALL parameterized replaceable events, not just videos
- Frontend caching may show old duplicates even after relay is cleaned - hard refresh
References