Regex public feeds in nostr

Silicology July 5, 2026
Source

The cleanest way to implement public regex feeds in Nostr is to treat them as saved queries, not as a new Nostr protocol feature.

There are two common approaches.

1. Client-side filtering (recommended)

A user subscribes to a normal Nostr feed, and the client filters events locally using a regex.

For example:

Feed: Rust Jobs

Regex:
(?i)\b(rust|leptos|tokio|wasm)\b

The relay sends all text notes, and the client only displays matching events.

Advantages:

Disadvantages:

Rust example:

use regex::Regex;

let regex = Regex::new(r"(?i)\brust\b").unwrap();

if regex.is_match(&event.content) {
    println!("Matched!");
}

2. Relay-side filtering (better for public feeds)

Imagine a relay dedicated to public feeds.

Users create feeds like:

rust
ai
india
bitcoin

Each feed stores

Feed ID
Regex
Owner
Description

When someone subscribes:

REQ
[
   "subid",
   {
      "kinds":[1],
      "#feed":["rust"]
   }
]

The relay translates

feed "rust"

↓

(?i)\b(rust|cargo|tokio|leptos)\b

and only streams matching events.

This greatly reduces bandwidth.

Representing feeds in Nostr

You could publish feeds as a custom event.

Example:

{
  "kind": 30078,
  "content": "",
  "tags": [
    ["d", "rust"],
    ["title", "Rust Developers"],
    ["regex", "(?i)\\b(rust|cargo|tokio|leptos)\\b"]
  ]
}

Clients discover these feed definitions and allow users to subscribe.

Relay implementation (Rust)

Using the regex crate:

use regex::Regex;

struct Feed {
    id: String,
    regex: Regex,
}

fn matches(feed: &Feed, content: &str) -> bool {
    feed.regex.is_match(content)
}

When an event arrives:

for feed in feeds {
    if feed.regex.is_match(&event.content) {
        send_to_subscribers(feed.id.clone(), event.clone());
    }
}

Making it efficient

Compiling regex for every event is slow.

Instead:

struct Feed {
    id: String,
    regex: Regex,
}

Compile once when the feed is created.

Then reuse:

feed.regex.is_match(&event.content)

Scaling to thousands of feeds

Checking every regex against every event is O(events × feeds), which becomes expensive.

A better pipeline is:

Incoming Event
      │
      ▼
Tokenize words
      │
      ▼
Candidate feed lookup
      │
      ▼
Run regex only on candidate feeds
      │
      ▼
Send to subscribers

For example:

Event:

"I love Rust and Tokio."

Tokens:

rust
tokio
love

↓

Candidate feeds:

Rust
Async
Programming

↓

Run regex on only those feeds.

An inverted index (keyword → feed IDs) drastically reduces the number of regex evaluations.

Even faster alternative

If most feeds are keyword-based rather than requiring full regex power, use the aho-corasick crate. It matches thousands of patterns simultaneously in a single pass and is significantly faster than evaluating many independent regexes. You can reserve regex evaluation only for feeds that genuinely need complex expressions.

Recommendation

For a public, community-driven feed system:

This design is bandwidth-efficient, scales much better than naive regex matching, and remains compatible with the existing Nostr event model while allowing anyone to publish and share reusable public feeds.

Discussion in the ATmosphere

Loading comments...