Posted by: Matt Lewis

Published:

Illustration of a FreshRSS search expression being turned into an automatic filter rule.

FreshRSS Tips: Turn Search Into a Rules Engine

It is easy to treat the FreshRSS search box like every other search box: type a word, scan the results, move on.

That misses most of what it can do. The box accepts a small query language, and FreshRSS builds its best automation on top of it. The same expression that finds articles can run automatically on everything a feed receives. Save it and you have a virtual feed; share it and another application can subscribe to the result as RSS.

Who This Is For
This is a deep dive into the FreshRSS search engine. It assumes you already have FreshRSS running. If you don’t, start with my Softaculous Spotlight on FreshRSS, which covers the install and a couple of important steps that will bite you if you skip them.

The Short Version

  • FreshRSS search supports field operators, boolean OR, negation, parentheses, ISO 8601 date intervals and regular expressions
  • The same syntax powers filter actions, which run automatically on articles as they arrive
  • Filter actions can mark articles as read or favorite, or apply a label; where you configure them depends on the action
  • Saved searches are called user queries, and one query can be referenced inside another with search: or S:
  • A user query can be republished at its own URL as HTML, RSS or OPML, which turns FreshRSS into a filtering layer for other software

1. The Operators, In One Place

Keep this section handy; everything else builds on it.

There is no space between an operator and its value. intitle:kubernetes works. intitle: kubernetes does not.

Multi-word values go in single or double quotes: author:'Bruce Schneier'.

FreshRSS search operators
OperatorWhat it matchesExample
intitle:Article titleintitle:release
intext:Article bodyintext:'zero day'
inurl:Article URLinurl:/security/
author:Author fieldauthor:'Simon Willison'
#tagTags published by the feed itself#linux or #'home lab'
label:A label you appliedlabel:Research
labels:Any of several labels, comma separatedlabels:'Research,Reference'
L:Label by numeric ID, or L:* for any labelL:12 or L:*
f:Feed by numeric IDf:42 or f:42,43,44
c:Category by numeric IDc:7
e:A specific article by IDe:1639310674957894
search:Another saved query, by namesearch:'Security Watch'
S:Another saved query, by numeric IDS:3
date:When FreshRSS discovered the articledate:P7D
pubdate:When the publisher says it was publishedpubdate:2026-09
mdate:When the server last modified the recordmdate:P1D
userdate:When you last touched it, e.g. read or starreduserdate:P1D

And the glue that holds them together:

SyntaxMeaningExample
(space)AND. Multiple criteria all have to matchintitle:php intext:security
OREither side matches. Must be uppercaseauthor:alice OR author:bob
! or -Negation. Prefix any operator or bare word!intitle:sponsored
( )Grouping(f:12 OR f:13) intitle:release
/.../Regular expressionintitle:/^Lo+l/i

A couple of things that will bite you eventually:

Finding the numeric IDs

f: and c: want numbers, and FreshRSS does not show them prominently. The quickest way is to click a feed or category in the sidebar and read the ID out of the address bar. There is also a small extension called ShowFeedID that puts them in the interface permanently, which is worth installing if you write a lot of these.


2. Dates Are Better Than You Think

ISO 8601 intervals, which sound scarier than they are.

The date operators accept fixed dates, ranges, and durations relative to right now. That last option is usually the most useful.

A duration starts with P, and T separates the date part from the time part. So P3D is three days, PT5H is five hours, and P1DT1H is one day and one hour.

ExpressionMeans
date:P1DDiscovered in the last 24 hours
date:PT30MDiscovered in the last thirty minutes
date:P2WDiscovered in the last two weeks
date:P6MDiscovered in the last six months
!date:P1MDiscovered more than a month ago
date:P1Y !date:P1MBetween one year and one month ago
pubdate:2026-09Published in September 2026
date:2026-01/2026-03Discovered between January and March 2026
date:2026-06/Discovered after June 2026
date:/2026-06Discovered before June 2026
Discovery Date vs Publication Date
date: is when your FreshRSS first saw the article. pubdate: is what the publisher claimed. They diverge constantly, because plenty of feeds backdate, republish, or lie. Purging is based on discovery date, so date: is usually the one you want when you are reasoning about what is still in your database.

There is one syntax that looks like it should work and does not: date:/P1M is not supported. Use the negation form !date:P1M instead.


3. Use Narrow Regular Expressions

Anchor patterns carefully and test them against real feed data.

Regular expressions go between forward slashes, and they work on intitle:, author:, inurl: and #tag. Add i for case-insensitive, m for multiline.

Match a version number in a release announcement

Search
intitle:/\bv?\d+\.\d+(\.\d+)?\b/
Catches Release 4.2 and v4.2.1 without matching every article that happens to contain the number 4.

Match an exact author, not a substring

Search
author:/^Alice Dupont$/im
author: holds one author per line, so m anchors ^ and $ to each line rather than to the whole field. Without it, a co-authored article will not match.

Find articles with an empty body

Search
intext:/^\s*$/
Useful for spotting feeds that have started publishing title-only entries, which is usually the signal that you want full-text extraction turned on for that feed.

One detail explains why a regex can behave differently as a filter than it did in search:

Two Different Regex Engines
Filter actions (auto-read, auto-favorite) always evaluate regex with PHP’s preg_match. Search box regex is handed to your database, so the exact dialect depends on whether you are running MySQL, MariaDB, PostgreSQL or SQLite. A pattern that works perfectly in the search box can behave differently once you paste it into a filter. Test the pattern in the place you intend to use it.

Three habits that keep regex filters sane:

Anchor them. An unanchored pattern matches anywhere in the field. intitle:/news/ will happily match “Renews”, “Newsletter” and “Bad news.” Use \b for word boundaries. FreshRSS translates \b for PostgreSQL automatically, so it is safe to use everywhere.

Escape your slashes. A literal / inside the pattern has to be written \/. This bites people writing URL patterns constantly.

Reach for plain operators first. intitle:sponsored OR intitle:'paid post' is easier to read, easier to fix six months from now, and usually faster than one clever regex that does the same job.


4. Kill the Noise Before You See It

Filter actions: the single highest-value feature in FreshRSS.

A filter action is a search expression that runs automatically against articles as they arrive.

Open any feed’s settings from Subscription management and you will find a box labeled Filter actions. Write one expression per line. FreshRSS combines the lines with OR, so any matching line triggers the action. Each line remains an independent rule instead of becoming part of one large expression.

Filters Run On Arrival by Default
New rules normally affect articles as they arrive. FreshRSS 1.28 and later also has an Also apply filters to existing articles checkbox when you save read filters. Preview the matches first; applying a broad rule to your backlog can mark a lot of articles as read at once.

There is a Preview filters on existing articles link right below the box. Use it before saving a rule, especially if you plan to apply it to existing articles. The preview uses database search, so a complex regular expression can match differently when the live filter runs through PHP.

Here are the rules that earn their keep.

Filter sponsored and affiliate posts

Mark as read
Feed settings → Filter actions
intitle:sponsored intitle:'paid post' intitle:'promoted' intext:'this post is sponsored' intext:'in partnership with' intext:'affiliate links'
Disclosure language tends to repeat across publishers. Add another line when you find wording the filter misses.

Suppress podcast and video entries from a text feed

Mark as read
Feed settings → Filter actions
intitle:/^(podcast|video|watch|livestream)\b/i intitle:'episode' inurl:/podcast/ inurl:/video/
Some sites put articles, podcasts, and videos in one feed. Use this when you only want the written posts.

Skip the weekly roundup posts

Mark as read
Feed settings → Filter actions
intitle:/\b(weekly|monthly)\s+(roundup|recap|digest|wrap.?up)\b/i intitle:/^(this week|last week) in\b/i intitle:/^week(ly)? (notes|links)\b/i
Use this when weekly recap posts repeat links you already saw. The entries remain searchable without staying unread.

Strip one topic out of a general news feed

Mark as read
Feed settings → Filter actions
inurl:/sport/ inurl:/celebrity/ inurl:/horoscope/ intitle:/\b(transfer window|match report|box score)\b/i
URL matching is more reliable than title matching here, because most news sites put the section in the path. Check the actual URLs on your feed before assuming the structure.

Auto-read the recurring housekeeping posts

Mark as read
Feed settings → Filter actions
intitle:/^(scheduled maintenance|resolved|monitoring)\b/i intitle:/\bnightly build\b/i intitle:/^\[?(bot|automated)\]?/i
Status pages, changelogs and forum feeds generate a lot of these. You want the feed subscribed so you can search it later, but you do not need to be notified about every routine entry.
Choose the Right Scope
You can mark articles as read with a rule on one feed, a whole category, or every feed. Put a repeated category-specific pattern on the category; reserve Configuration → Reading for rules you really want everywhere.

5. Now Flip It Around

So far, we’ve hidden articles you don’t want. The same filters can star the ones you do.

FreshRSS puts its three filter actions in different parts of the interface:

ActionWhere you configure itScope
Mark as readFeed settings → Filter actionsThat one feed
Mark as readCategory settings → Filter actionsThat category
Mark as readConfiguration → Reading → Mark an article as read…Every feed
Mark as favoriteConfiguration → Reading → Mark an article as favourite…Every feed
Apply a labelSubscription management → Label management → Add this label to new articlesEvery feed

That table is the answer to “why can’t I set up auto-favorite on a single feed?” You scope it yourself, by putting f: or c: into the expression.

Star security advisories for software you run

Favorite
Configuration → Reading → Mark an article as favourite…
c:7 intitle:/\b(nginx|postfix|openssl|wordpress|php)\b/i intitle:/\bCVE-\d{4}-\d{4,7}\b/ intitle:/\b(remote code execution|privilege escalation|actively exploited)\b/i intitle:/\b(zero.?day|0.?day)\b/i
The CVE line catches standard identifiers. Replace the category and product names with the sources and software you monitor.

Watch for mentions of your company or product

Favorite
Configuration → Reading → Mark an article as favourite…
intitle:/\bhawk\s?host\b/i intext:/\bhawk\s?host\b/i
Anchor common words with \b to avoid unrelated matches. Replace Hawk Host with your own company or product name.

Label incoming end-of-life and deprecation notices

Label
Label management → Add this label to new articles
intitle:/\b(end.of.life|EOL|deprecat\w+|sunset(ting)?|discontinu\w+)\b/i intitle:/\bbreaking change\b/i
Collect these notices under one label and review them monthly instead of relying on the unread queue.

6. Labels Are Workflow States, Not Decoration

A short, consistent list is easier to maintain.

It is tempting to use labels as topics, much like browser bookmark folders. Categories and feed tags already handle topics. Labels are more useful as states that show where an article is in your process.

A practical starting set:

LabelUse it for
Read LaterTriaged, but not yet read
ResearchRelevant to something you are actively working on
ReferenceWorth keeping permanently, with no action needed
ActionRequires a follow-up
DoneHandled and kept for the record

You can then combine those states in saved queries.

Action queue

Saved query
label:Action !label:Done
Shows items that need action and have not been marked done.

Research untouched for sixty days

Saved query
label:Research !userdate:P60D
Find research articles you have not touched in sixty days, then keep them as references or remove the label.

Triage backlog

Saved query
Open the Favourites view, then run this search
!L:* userdate:P30D
FreshRSS has no favorite-state search operator. Starting from the Favourites view supplies that scope; the expression then finds unlabeled items touched in the last 30 days.
Tags and Labels Are Not the Same Thing
#tag searches the categories the publisher put in the feed. label: searches the labels you applied. Both are useful, and mixing them up is the most common reason a search returns nothing when you were sure it should.

7. Saved Queries Are Virtual Feeds

FreshRSS calls them user queries. They are considerably more than bookmarks.

Once you have an expression you like, save it. Run the search, open the dropdown next to the state buttons, and bookmark it. It now appears in that dropdown permanently.

FreshRSS stores the query, not a frozen set of results. It runs the search again every time you open it, so today’s articles naturally differ from next month’s. Think of it as a feed assembled from your other feeds on demand.

Unread weekly review

Saved query
date:P7D !label:Done
Creates one seven-day view across all subscribed feeds while excluding completed items.

Long-form articles from selected categories

Saved query
(c:3 OR c:5) !intitle:/^(link|links|tab dump)\b/i !inurl:/podcast/
Two feed categories, excluding the formats you do not want to sit down with. Swap in your own c: numbers.

Queries can reference other queries

Saved queries can also call one another, which is where the language becomes much easier to maintain.

You can refer to a saved query from inside another query, by name with search: or by ID with S:. FreshRSS substitutes the referenced query’s expression into the new one before it runs.

So build small, named pieces and combine them:

Compose queries from named building blocks

Saved query
Noise → intitle:sponsored OR intitle:/roundup/i OR inurl:/podcast/ Security Sources → c:7 OR f:42 OR f:88 Urgent → intitle:/\bCVE-\d{4}-\d{4,7}\b/i OR intitle:/actively exploited/i Morning Review → search:'Security Sources' (search:Urgent OR date:P1D) !search:Noise
Save the first three as separate queries. The fourth combines them, so changing Noise later also changes Morning Review.

Fix your definition of “noise” in one place and every query that references it improves with it. Think of saved-query references as small reusable functions; they are much easier to maintain than six near-identical expressions.

Negating a Reference
!search:Noise and !(S:1 OR S:2) both work, so you can subtract a whole saved query from a result set. Worth knowing before you write the long version by hand.

8. Publish a Query as a Real RSS Feed

Turn a private search into an input another tool can consume.

A saved query can be exposed at its own URL, in HTML, RSS, or OPML.

Use this when another tool should receive only a filtered subset of your subscriptions:

  1. FreshRSS fetches, deduplicates, filters, and labels the source feeds.
  2. A saved query selects the articles you want, such as urgent security posts from the last seven days.
  3. The published RSS URL sends those results to Slack, n8n, a static site, or another reader.

To turn it on:

Sharing a User Query

Two prerequisites and one checkbox.

1
Enable the API
Under Authentication, make sure API access is enabled. Query sharing rides on the same machinery, so without it the sharing options will not do anything.
2
Open the user query configuration page
Go to Configuration → User queries. Each saved query has its own settings block.
3
Tick the sharing option you want

Enable sharing by HTML and RSS gives you a readable page and a subscribable feed. Enable sharing by OPML publishes the list of feeds behind the query instead of the articles.

FreshRSS generates a URL containing a token unique to that query, such as https://rss.yourdomain.com/p/api/query.php?user=alice&t=TOKEN&f=rss.

4
Tune the output with URL parameters

The generated URL accepts extras:

  • f=html, f=rss or f=opml: output format
  • hours=24: only articles newer than this many hours
  • nb=50: how many articles to return, up to the user’s RSS output limit
  • offset=50: skip this many for pagination
  • order=ASC or order=DESC: sort direction
  • search=...: apply another filter on top of the saved query

So one saved query can serve several different downstream consumers just by varying the URL.

These URLs Are Public
Anyone with the link can read the output. The token is the only thing protecting it, so treat a shared query URL like a password and do not paste one into a public issue tracker. If a link leaks, stop sharing that query and create a replacement with a fresh URL.
OPML Sharing Has Limits
Sharing as OPML only works for queries scoped to all feeds, a category, or a single feed. Queries based on labels, favorites, or important feeds cannot be shared as OPML. This prevents a shared query from exposing feeds you never intended to publish.

9. Two Settings That Prevent a Lot of Duplicates

Use built-in duplicate detection before writing another filter.

If you follow more than a handful of news sources, you have the syndication problem: one wire story, eleven outlets, eleven entries in your reader.

Configuration → Reading has options for exactly this, under Mark an article as read…:

Two neighboring settings help keep the unread count under control:

Set the Unread Ceiling Before You Need It
Nobody reads four thousand articles. They declare bankruptcy and mark everything read, which loses the handful that mattered too. A ceiling of a few hundred means the backlog stays a list you might actually work through.

10. Before You Trust a Rule, Test It

A filter that marks the wrong thing as read is worse than no filter at all, because the mistake is almost invisible.

Three checks, in order of effort:

Run it in the search box first. Every filter expression is a valid search expression. Paste it in, look at what comes back, and pay attention to what you did not expect.

Use the preview link. The Preview filters on existing articles link under the Filter actions box searches what is already in your database and shows the matches in a new window. It is a useful check, though complex regex can behave differently in the live filter’s PHP engine.

Use the global view for counts. FreshRSS’s global view shows how many articles per feed match a search expression. If one feed matches ninety percent of its own articles, the rule is probably too broad.

Then give it a week and check the results. Search for the expression, sort by date, and confirm the recent matches are all things you meant to filter.


Quick Reference Questions

Check these first.

You put a space after the operator: intitle: linux is parsed as a search for intitle: plus the word linux. Remove the space.

You used lowercase or, which is treated as a literal search word rather than an operator.

You are filtering by state without realizing it. The default view shows unread articles only, so an article you have already read will not appear until you toggle the state filters at the top.

You used #tag when you meant label:. # searches the publisher’s tags; label: searches yours.

By default, they run as articles arrive. If you use FreshRSS 1.28 or later, you can tick Also apply filters to existing articles when saving a read filter. Preview it first, because it can mark a large backlog as read. The option also works for global favorite filters; it is not a general replay for every filter action.
Not directly. Favorite and label filter actions are configured globally rather than per feed. Add a feed or category constraint to the expression yourself; for example, f:42 intitle:/CVE/ only stars matches in feed 42.
They are combined with OR. Any single line matching is enough to fire the action. Each line is an independent rule, so it is fine to have one line handling sponsored posts and another handling podcasts, with no relationship between them.

date: is when your FreshRSS instance first saw the article. pubdate: is the date in the feed itself.

Purging uses discovery date, so date: is what to reason with when you are thinking about retention. pubdate: is what to use when you care about when something was actually written, which matters for feeds that backdate or republish old content.

Not a hard one. The practical limit is how many fit legibly in the dropdown. If you find yourself with a long list, that is usually a sign to consolidate using search: references rather than maintaining many similar queries side by side.

UPSTREAM DOCS
The Official Filter Reference
The FreshRSS project documents the full search syntax, including edge cases this article skipped. Keep it nearby while building more complicated filters.
Read the Docs
Ready to get started? Build your site from
$1.99/mo
GET STARTED NOW