Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Filters

A Filter describes a set of events without prescribing which relay should answer the query. Keeping those concerns separate lets the same filter target the whole pool, one relay, or a manually selected group.

Filter composition follows two rules:

  • constraints inside one filter are combined with AND;
  • multiple filters in one request are combined with OR.

For example, one filter containing an author and a text-note kind asks for text notes by that author. Two separate filters, one for the author and one for the kind, also match every event by that author and every text note by anyone.

A filter can constrain IDs, authors, kinds, timestamps, tags, and the number of stored results requested from each relay.

Construct a bounded filter

The example limits the author, kind, lower time boundary, and number of stored results requested from each relay:

Rust
use nostr::prelude::*;

fn filter_example() {
    let public_key = Keys::generate().public_key();
    let filter = Filter::new()
        .author(public_key)
        .kind(Kind::TextNote)
        .since(Timestamp::now())
        .limit(20);

    println!("Filter: {}", filter.as_json());
}
Python
from nostr_sdk import Event, EventBuilder, Filter, Kind, KindStandard, Keys, PublicKey, Timestamp

def filter_example() -> None:
    public_key = Keys.generate().public_key()
    filter = (
        Filter()
        .author(public_key)
        .kind(Kind.from_std(KindStandard.TEXT_NOTE))
        .since(Timestamp.now())
        .limit(20)
    )

    print(f"Filter: {filter.as_json()}")
JavaScript
Node.js
import {
    Event,
    EventBuilder,
    Filter,
    Kind,
    KindStandard,
    Keys,
    PublicKey,
    Timestamp,
} from "@nostrdevkit/nostr-sdk-node";

function filterExample() {
    const publicKey = Keys.generate().publicKey();
    const filter = new Filter()
        .author(publicKey)
        .kind(Kind.fromStd(KindStandard.TextNote))
        .since(Timestamp.now())
        .limit(20n);

    console.log("Filter:", filter.asJson());
}
Web
import {
    Event,
    EventBuilder,
    Filter,
    Kind,
    KindStandard,
    Keys,
    PublicKey,
    Timestamp,
    uniffiInitAsync,
} from "@nostrdevkit/nostr-sdk-web";

await uniffiInitAsync();

function filterExample() {
    const publicKey = Keys.generate().publicKey();
    const filter = new Filter()
        .author(publicKey)
        .kind(Kind.fromStd(KindStandard.TextNote))
        .since(Timestamp.now())
        .limit(20n);

    console.log("Filter:", filter.asJson());
}
React Native
import {
    Event,
    EventBuilder,
    Filter,
    Kind,
    KindStandard,
    Keys,
    PublicKey,
    Timestamp,
} from "@nostrdevkit/nostr-sdk-react-native";

export function filterExample() {
    const publicKey = Keys.generate().publicKey();
    const filter = new Filter()
        .author(publicKey)
        .kind(Kind.fromStd(KindStandard.TextNote))
        .since(Timestamp.now())
        .limit(20n);

    console.log("Filter:", filter.asJson());
}
Kotlin
import org.nostrdevkit.sdk.*

fun filterExample() {
    val publicKey = Keys.generate().publicKey()
    val filter = Filter()
        .author(publicKey)
        .kind(Kind.fromStd(KindStandard.TEXT_NOTE))
        .since(Timestamp.now())
        .limit(20u)

    println("Filter: ${filter.asJson()}")
}
Swift
import NostrSDK

func filterExample() throws {
    let publicKey = Keys.generate().publicKey()
    let filter = Filter()
        .author(author: publicKey)
        .kind(kind: Kind.fromStd(e: .textNote))
        .since(timestamp: Timestamp.now())
        .limit(limit: 20)

    print("Filter: \(try filter.asJson())")
}
C#
using Nostr.Sdk;

public static class CoreExample
{

    public static void FilterExample()
    {
        var publicKey = Keys.Generate().PublicKey();
        var filter = new Filter()
            .Author(publicKey)
            .Kind(Kind.FromStd(KindStandard.TextNote))
            .Since(Timestamp.Now())
            .Limit(20);

        Console.WriteLine($"Filter: {filter.AsJson()}");
    }
}

This asks each relay for at most 20 text notes by the selected key at or after the timestamp. All four constraints are in one filter, so they are combined with AND.

Compose AND and OR deliberately

Suppose an application needs text notes from Alice or Bob. One filter with both authors expresses that request:

authors = [Alice, Bob] AND kinds = [TextNote]

Suppose it instead needs Alice’s text notes plus Bob’s metadata. That requires two filters:

(authors = [Alice] AND kinds = [TextNote])
OR
(authors = [Bob] AND kinds = [Metadata])

Each additional filter is another OR branch and can expand the response.

Tag filters follow the same rule. Multiple accepted values for one tag name are alternatives within that constraint; constraints for different tag names must all match. Use the typed single-letter tag APIs where available instead of assembling # field names as strings.

Time windows

since and until are inclusive. Use them to bound a request to the history relevant to the application.

When maintaining local history, reconcile a bounded filter with Negentropy sync instead of walking through relay history page by page. Sync compares event IDs and transfers only the missing events.

For a live subscription, since also prevents an unbounded initial history. limit bounds stored results from each relay but does not replace a time window.

Understand pool-wide results

The limit is applied by each relay, not globally across the pool. Results can overlap or arrive out of order; the SDK validates and deduplicates them by event ID. Relay selection remains separate and is added by the request target.

Filters become network work only when paired with a client and request target.