Posted by: Matt Lewis
Published:

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.
search: or S: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'.
| Operator | What it matches | Example |
|---|---|---|
intitle: | Article title | intitle:release |
intext: | Article body | intext:'zero day' |
inurl: | Article URL | inurl:/security/ |
author: | Author field | author:'Simon Willison' |
#tag | Tags published by the feed itself | #linux or #'home lab' |
label: | A label you applied | label:Research |
labels: | Any of several labels, comma separated | labels:'Research,Reference' |
L: | Label by numeric ID, or L:* for any label | L:12 or L:* |
f: | Feed by numeric ID | f:42 or f:42,43,44 |
c: | Category by numeric ID | c:7 |
e: | A specific article by ID | e:1639310674957894 |
search: | Another saved query, by name | search:'Security Watch' |
S: | Another saved query, by numeric ID | S:3 |
date: | When FreshRSS discovered the article | date:P7D |
pubdate: | When the publisher says it was published | pubdate:2026-09 |
mdate: | When the server last modified the record | mdate:P1D |
userdate: | When you last touched it, e.g. read or starred | userdate:P1D |
And the glue that holds them together:
| Syntax | Meaning | Example |
|---|---|---|
| (space) | AND. Multiple criteria all have to match | intitle:php intext:security |
OR | Either side matches. Must be uppercase | author:alice OR author:bob |
! or - | Negation. Prefix any operator or bare word | !intitle:sponsored |
( ) | Grouping | (f:12 OR f:13) intitle:release |
/.../ | Regular expression | intitle:/^Lo+l/i |
A couple of things that will bite you eventually:
OR has to be uppercase. Lowercase or is treated as a plain search word.\( and \), or wrapped in quotes like "a (b)".<, & and " for you, so you can search for 'A & B' without escaping anything. The exception is regex, where nothing is encoded, which is exactly what lets you search for markup.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.
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.
| Expression | Means |
|---|---|
date:P1D | Discovered in the last 24 hours |
date:PT30M | Discovered in the last thirty minutes |
date:P2W | Discovered in the last two weeks |
date:P6M | Discovered in the last six months |
!date:P1M | Discovered more than a month ago |
date:P1Y !date:P1M | Between one year and one month ago |
pubdate:2026-09 | Published in September 2026 |
date:2026-01/2026-03 | Discovered between January and March 2026 |
date:2026-06/ | Discovered after June 2026 |
date:/2026-06 | Discovered before June 2026 |
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.
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.
One detail explains why a regex can behave differently as a filter than it did in search:
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.
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.
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.
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:
| Action | Where you configure it | Scope |
|---|---|---|
| Mark as read | Feed settings → Filter actions | That one feed |
| Mark as read | Category settings → Filter actions | That category |
| Mark as read | Configuration → Reading → Mark an article as read… | Every feed |
| Mark as favorite | Configuration → Reading → Mark an article as favourite… | Every feed |
| Apply a label | Subscription management → Label management → Add this label to new articles | Every 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.
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:
| Label | Use it for |
|---|---|
| Read Later | Triaged, but not yet read |
| Research | Relevant to something you are actively working on |
| Reference | Worth keeping permanently, with no action needed |
| Action | Requires a follow-up |
| Done | Handled and kept for the record |
You can then combine those states in saved queries.
#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.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.
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:
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.
!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.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:
To turn it on:
Two prerequisites and one checkbox.
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.
The generated URL accepts extras:
f=html, f=rss or f=opml: output formathours=24: only articles newer than this many hoursnb=50: how many articles to return, up to the user’s RSS output limitoffset=50: skip this many for paginationorder=ASC or order=DESC: sort directionsearch=...: apply another filter on top of the saved querySo one saved query can serve several different downstream consumers just by varying the URL.
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:
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.
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.
f:42 intitle:/CVE/ only stars matches in feed 42.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.
search: references rather than maintaining many similar queries side by side.