Menu
Nostr Beginner 5 min read

Event Filtering

Query events from Nostr relays using filters. Match by author, kind, tags, and time ranges.

filters queries subscriptions REQ tags

Event Filtering

Filters are how you query Nostr relays for specific events. Instead of fetching everything, you describe what you want, and the relay returns matching events.

Filter Structure

A filter is a JSON object specifying match criteria:

{
  "ids": ["event-id-1", "event-id-2"],
  "authors": ["pubkey-1", "pubkey-2"],
  "kinds": [1, 7],
  "#e": ["referenced-event-id"],
  "#p": ["referenced-pubkey"],
  "#t": ["hashtag"],
  "since": 1704067200,
  "until": 1704153600,
  "limit": 100
}

All conditions are AND-ed together. Values within arrays are OR-ed.

Filter Fields

FieldTypeMatches
idsstring[]Event IDs (exact or prefix)
authorsstring[]Author pubkeys
kindsint[]Event kinds
#<tag>string[]Tag values (e.g., #e, #p, #t)
sinceintEvents created after timestamp
untilintEvents created before timestamp
limitintMaximum events to return

Common Query Patterns

Fetch User’s Notes

{
  "authors": ["pubkey-hex"],
  "kinds": [1],
  "limit": 50
}

Fetch User’s Profile

{
  "authors": ["pubkey-hex"],
  "kinds": [0],
  "limit": 1
}

Fetch Replies to a Note

{
  "kinds": [1],
  "#e": ["note-event-id"],
  "limit": 100
}

Fetch Reactions to a Note

{
  "kinds": [7],
  "#e": ["note-event-id"]
}

Fetch Notes with Hashtag

{
  "kinds": [1],
  "#t": ["bitcoin"],
  "limit": 50
}

Fetch Recent Events (Firehose)

{
  "kinds": [1],
  "since": 1704067200,
  "limit": 100
}

Fetch Zaps for a Note

{
  "kinds": [9735],
  "#e": ["note-event-id"]
}

Subscription Protocol

Send filters in a REQ message:

["REQ", "subscription-id", <filter1>, <filter2>, ...]

Multiple filters = OR (match any filter).

Python Example

import json
import websockets

async def subscribe_to_notes(relay_url, pubkeys):
    async with websockets.connect(relay_url) as ws:
        # Create subscription
        sub_id = "notes-sub"
        filter = {
            "authors": pubkeys,
            "kinds": [1],
            "since": int(time.time()) - 86400,  # Last 24 hours
            "limit": 100
        }

        await ws.send(json.dumps(["REQ", sub_id, filter]))

        # Receive events
        events = []
        while True:
            msg = json.loads(await ws.recv())

            if msg[0] == "EVENT" and msg[1] == sub_id:
                events.append(msg[2])
            elif msg[0] == "EOSE" and msg[1] == sub_id:
                break

        # Close subscription
        await ws.send(json.dumps(["CLOSE", sub_id]))

        return events

JavaScript Example

import { SimplePool } from 'nostr-tools';

const pool = new SimplePool();

const events = await pool.querySync(
  ['wss://relay.damus.io', 'wss://nos.lol'],
  {
    authors: ['pubkey-hex'],
    kinds: [1],
    limit: 50
  }
);

console.log(`Found ${events.length} events`);

Multiple Filters (OR)

Request different event types in one subscription:

["REQ", "combined-sub",
  {"authors": ["pubkey"], "kinds": [1], "limit": 50},
  {"authors": ["pubkey"], "kinds": [0], "limit": 1},
  {"kinds": [7], "#p": ["pubkey"], "limit": 20}
]

This fetches:

  • User’s 50 recent notes
  • User’s profile
  • 20 reactions mentioning user

Prefix Matching

IDs and authors support prefix matching:

{
  "ids": ["abc"],
  "authors": ["def"]
}

Matches events where ID starts with “abc” or author starts with “def”.

Useful for:

  • Efficient queries with partial IDs
  • Targeting groups of related events

Time-Based Queries

Events Since Timestamp

{
  "kinds": [1],
  "since": 1704067200
}

Events Before Timestamp

{
  "kinds": [1],
  "until": 1704153600
}

Events in Time Range

{
  "kinds": [1],
  "since": 1704067200,
  "until": 1704153600
}

Pagination

Relays don’t have built-in pagination. Use limit and until:

async def paginate_events(relay_url, pubkey, page_size=50):
    all_events = []
    until = None

    while True:
        filter = {
            "authors": [pubkey],
            "kinds": [1],
            "limit": page_size
        }
        if until:
            filter["until"] = until

        events = await fetch_events(relay_url, filter)

        if not events:
            break

        all_events.extend(events)

        # Set until to oldest event's timestamp - 1
        until = min(e["created_at"] for e in events) - 1

    return all_events

Tag Queries

Any single-letter tag can be queried:

FilterMatches
#e["e", "..."] tags (event references)
#p["p", "..."] tags (pubkey mentions)
#t["t", "..."] tags (hashtags)
#a["a", "..."] tags (replaceable refs)
#d["d", "..."] tags (identifiers)

Example: Find All Mentions

{
  "kinds": [1],
  "#p": ["your-pubkey"],
  "since": 1704067200
}

Query Optimization

DO

  • Use limit to avoid massive responses
  • Use since to bound queries temporally
  • Query multiple relays and dedupe
  • Cache results when appropriate

DON’T

  • Query without any filters (firehose)
  • Request unlimited events
  • Use overly broad time ranges
  • Ignore EOSE (end of stored events)

Subscription Management

Keep subscriptions minimal:

class SubscriptionManager:
    def __init__(self):
        self.active_subs = {}

    async def subscribe(self, ws, sub_id, filters):
        if sub_id in self.active_subs:
            await self.unsubscribe(ws, sub_id)

        await ws.send(json.dumps(["REQ", sub_id, *filters]))
        self.active_subs[sub_id] = filters

    async def unsubscribe(self, ws, sub_id):
        if sub_id in self.active_subs:
            await ws.send(json.dumps(["CLOSE", sub_id]))
            del self.active_subs[sub_id]

    async def close_all(self, ws):
        for sub_id in list(self.active_subs.keys()):
            await self.unsubscribe(ws, sub_id)

Machine-Readable Summary

{
  "topic": "nostr-filters",
  "audience": "ai-agents",
  "prerequisites": ["nostr-events", "websockets"],
  "key_concepts": [
    "filter-fields",
    "tag-queries",
    "time-ranges",
    "pagination",
    "subscription-management"
  ],
  "code_examples": ["python", "javascript"],
  "related": [
    "/learn/nostr/relays",
    "/learn/nostr/events",
    "/learn/nostr/specs/nip-01"
  ]
}