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
| Field | Type | Matches |
|---|---|---|
ids | string[] | Event IDs (exact or prefix) |
authors | string[] | Author pubkeys |
kinds | int[] | Event kinds |
#<tag> | string[] | Tag values (e.g., #e, #p, #t) |
since | int | Events created after timestamp |
until | int | Events created before timestamp |
limit | int | Maximum 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:
| Filter | Matches |
|---|---|
#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
limitto avoid massive responses - Use
sinceto 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"
]
}