Subscribing to Events
Once connected, you can subscribe to events using ndk.subscribe() by providing filters you can specify the events you're interested in.
More about how this all works in the dedicated section about subscription internals.
Subscribe
The ndk.subscribe() method accepts these parameters:
filters: A single or array ofNDKFilter. See NIP-01.opts?: Subscription options objectNDKSubscriptionOptions.autoStart?: Event handlers for that subscription.
import NDK from "@nostr-dev-kit/ndk";
const ndk = new NDK();
ndk.subscribe(
{ kinds: [1] }, // Filters
{ closeOnEose: true }, // Options (no explicit relays specified)
);Specifying Relays
By default, NDK will use the already connected relay set or provided through the signer. You can override this behavior by providing explicit relays in the relayUrls or relaySet options.
import NDK, {NDKRelaySet} from "@nostr-dev-kit/ndk";
const ndk = new NDK();
const explicitRelaySet = NDKRelaySet.fromRelayUrls(["wss://explicit.relay"], ndk);
ndk.subscribe(
{kinds: [7]}, // Filters
{
// Options object now includes relaySet
closeOnEose: true,
relaySet: explicitRelaySet,
},
);By default, NDK subscriptions use cross-subscription matching: when an event comes in from any relay, it's delivered to all subscriptions whose filters match, regardless of which relays the subscription was targeting.
Event Handlers
Handler Functions
TIP
The recommended way to handle events is to provide handler functions directly when calling ndk.subscribe(). This is done using the third argument (autoStart), which accepts an object containing onEvent, onEvents, and/or onEose callbacks.
Why is this preferred? Subscriptions can start receiving events (especially from a fast cache) almost immediately after ndk.subscribe() is called. By providing handlers directly, you ensure they are attached before any events are emitted, preventing potential race conditions where you might miss the first few events if you attached handlers later using .on().
import NDK, { type NDKEvent, type NDKRelay, type NDKSubscription } from "@nostr-dev-kit/ndk";
const ndk = new NDK();
ndk.subscribe(
{ kinds: [1] }, // Filters
{ closeOnEose: true }, // Options (no explicit relays specified)
{
// Direct handlers via autoStart parameter (now the 3rd argument)
onEvent: (event: NDKEvent, relay?: NDKRelay) => {
// Called for events received from relays after the initial cache load (if onEvents is used)
console.log("Received event from relay (id):", event.id);
},
onEvents: (events: NDKEvent[]) => {
// Parameter renamed to 'events'
console.log(`Received ${events.length} events from cache initially.`);
},
onEose: (subscription: NDKSubscription) => {
console.log("Subscription reached EOSE:", subscription.internalId);
},
},
);Attaching Handlers
You can also attach event listeners after creating the subscription using the .on() method.
WARNING
While functional, be mindful of the potential race condition mentioned above, especially if you rely on immediate cache results.
import NDK from "@nostr-dev-kit/ndk";
const ndk = new NDK();
const subscription = ndk.subscribe(
{ kinds: [1] }, // Filters
{ closeOnEose: true }, // Options (no explicit relays specified)
);
// Attach handlers later
subscription.on("event", (event) => {
console.log("Received event:", event.id);
});
subscription.on("eose", () => {
console.log("Initial events loaded");
});
// Remember to stop the subscription when it's no longer needed
// setTimeout(() => subscription.stop(), 5000);Functions
onEvent
The onEvent handler is called for every event received from relays or the cache.
onEvents
Using the onEvents handler provides an efficient way to process events loaded from the cache. When you provide onEvents:
- If NDK finds matching events in its cache synchronously when the subscription starts,
onEventsis called once with an array of all those cached events. - The
onEventhandler is skipped for this initial batch of cached events. onEventwill still be called for any subsequent events received from relays or later asynchronous cache updates.
This is ideal for scenarios like populating initial UI state, as it allows you to process the cached data in a single batch, preventing potentially numerous individual updates that would occur if onEvent were called for each cached item.
If you don't provide onEvents, the standard onEvent handler will be triggered for every event, whether it comes from the cache or a relay.
onEose
Called when the subscription is closed.
Targetting Relays
By default, NDK subscriptions use cross-subscription matching: when an event comes in from any relay, it's delivered to all subscriptions whose filters match, regardless of which relays the subscription was targeting.
The exclusiveRelay option allows you to create subscriptions that only accept events from their specified relays, ignoring events that match the filter but come from other relays.
import NDK from "@nostr-dev-kit/ndk";
const ndk = new NDK();
// Subscription that ONLY accepts events from relay-a.com
const exclusiveSub = ndk.subscribe(
{kinds: [7]},
{
relayUrls: ["wss://relay-a.com"],
exclusiveRelay: true, // 🔑 Key option
},
);
exclusiveSub.on("event", (event) => {
console.log("Event from relay-a.com:", event.content);
// This will ONLY fire for events from relay-a.com
// Events from relay-b.com or relay-c.com are rejected
});Without exclusiveRelay, subscriptions receive events from any relay (Cross-Subscription Matching).
More information, use-cases and examples of exclusive relays is available in the advanced exclusive relay documentation.
Code Snippets
More snippets and examples can be found in the snippets directory