A FreshRSS extension that periodically re-applies "Mark as read" filter rules to existing articles.
FreshRSS only applies "Mark as read" filter rules when new articles arrive during feed actualization. This means time-based rules like -pubdate:P7D/ (older than 7 days) never match existing articles — because they were newer than 7 days when they first arrived.
This extension hooks into the FreshRSS maintenance cycle and re-evaluates all configured "Mark as read" filter rules against existing unread articles. Rules are collected from three levels:
- Feed-level filters (configured per individual feed)
- Category-level filters (configured per category)
- Global filters (configured in user settings)
Each level can be independently enabled/disabled and has its own configurable run interval.
- Download or clone this repository.
- Copy the
xExtension-MarkAsReadExistingfolder into your FreshRSSextensions/directory:cp -r xExtension-MarkAsReadExisting /path/to/FreshRSS/extensions/
- Ensure correct ownership (for Docker setups, typically
www-data):chown -R www-data:www-data /path/to/FreshRSS/extensions/xExtension-MarkAsReadExisting
- Enable the extension in FreshRSS: Settings → Extensions → Mark As Read Existing → Enable
cd /path/to/FreshRSS/extensions
git clone https://github.com/bullitt186/FreshRSS-MarkAsReadExisting.git _tmp_mark_as_read
mv _tmp_mark_as_read/xExtension-MarkAsReadExisting .
rm -rf _tmp_mark_as_readOr create a symlink:
cd /path/to/FreshRSS/extensions
git clone https://github.com/bullitt186/FreshRSS-MarkAsReadExisting.git
ln -s FreshRSS-MarkAsReadExisting/xExtension-MarkAsReadExisting xExtension-MarkAsReadExistingNavigate to Settings → Extensions → Mark As Read Existing → Configure.
| Setting | Default | Description |
|---|---|---|
| Feed-level filters | Enabled, 60 min | Re-apply feed-level "mark as read" rules |
| Category-level filters | Enabled, 60 min | Re-apply category-level "mark as read" rules |
| Global filters | Enabled, 60 min | Re-apply global "mark as read" rules |
| Dry run | Off | Log matches without actually marking articles |
A Run now button triggers immediate filter application, bypassing interval timers. Useful for testing after changing filter rules.
Enable Dry run to see which articles would be marked as read without actually marking them. Check the FreshRSS log (Settings → Logs) for results like:
[MarkAsReadExisting] [DRY RUN] Feed "heise online" filter "-pubdate:P4D/": 215 articles
[MarkAsReadExisting] [DRY RUN] Total: 510 articles marked as read
- During each FreshRSS actualization (CLI cron or browser-triggered), the
freshrss_user_maintenancehook fires. - For each enabled level (feed/category/global), the extension checks if the configured interval has elapsed since the last run.
- It collects all filter rules with the "mark as read" action.
- For each filter rule, it queries unread articles and marks matching ones as read (using PHP-level
Entry->matches()for full filter compatibility). - Results are logged per filter with article counts.
| Filter | Effect |
|---|---|
-pubdate:P7D/ |
Mark articles older than 7 days as read |
-pubdate:P30D/ |
Mark articles older than 30 days as read |
intitle:sponsor |
Mark sponsored articles as read (retroactively) |
author:bot AND -pubdate:P1D/ |
Mark bot articles older than 1 day as read |
This extension uses the same filter syntax as FreshRSS's built-in "Mark as read" rules. See the FreshRSS filter documentation for the full syntax reference.
Tests require a local FreshRSS checkout at the sibling directory level:
cd /path/to/FreshRSS
./vendor/bin/phpunit -c /path/to/FreshRSS-MarkAsReadExisting/tests/phpunit.xml# PHPCS (PSR-12)
phpcs --standard=PSR12 xExtension-MarkAsReadExisting/extension.php
# PHPStan (level 10)
phpstan analyse xExtension-MarkAsReadExisting/extension.php- FreshRSS 1.20.0 or later
- PHP 8.1 or later
AGPL-3.0 — same as FreshRSS.