hellokunzai37 downloadsSubscribe to RSS and Atom feeds, read articles in a dedicated reader tab, and save any article to your vault as a Markdown note.
English · 简体中文
Subscribe to RSS, Atom and RSS 1.0 (RDF) feeds, read them in a dedicated reader tab inside Obsidian, and turn any article into a Markdown note in your vault.
Search for "Signal RSS" under Settings → Community plugins → Browse.
main.js, manifest.json and styles.css from the latest release.<your vault>/.obsidian/plugins/signal-rss/.Right-click a feed in the sidebar for refresh, mark-as-read, edit and delete. The menu behind a group header also carries OPML import and export, mark-all-read/unread for that group, and rename/delete for the group itself — deleting a group moves its feeds back to Ungrouped instead of deleting them, and renaming it takes them along.
Right-click the empty space under the subscription tree to create a group, or to add a feed without filing it into one. A group you create stays in the tree even while it holds no feeds — instead of an empty space it shows an Add feed… row, which pre-fills the group — until you delete it again.
The list only ever lives in the sidebar, so there is no position to choose: Signal RSS: Toggle the subscription list sidebar shows and hides it, and while it is hidden the reader's empty state offers a button that brings it back.
| Command | Description |
|---|---|
| Open RSS reader | Show the reader tab — empty until an article is picked |
| Refresh all feeds | Fetch every subscription |
| Add feed | Open the add-feed dialog |
| Import OPML file | Import subscriptions from an OPML file |
| Export OPML file | Write an OPML file to the vault root |
| Save current article as note | Save the article open in the reader |
| Mark all articles as read | Clear the unread state everywhere |
| Collapse all groups | Fold every group in the feed tree shut |
| Expand all groups | Unfold every group in the feed tree |
| Toggle the subscription list sidebar | Show or hide the list in the right sidebar |
The three commands that need something to act on — save the current article, collapse all groups, expand all groups — are hidden from the command palette while they would do nothing.
The settings page is split into four tabs.
| Setting | Description |
|---|---|
| Automatic refresh interval | Background polling interval in minutes; 0 turns it off |
| Request timeout | How long to wait for a feed before giving up (5–120 s) |
| Articles kept per feed | Upper bound on stored articles per feed; the oldest are dropped past it |
| Mark as read when opened | Clear the unread flag as soon as an article is opened |
| Fetch full text when opened | Fetch the article page when a feed only ships a summary |
| Fetch full text while refreshing | Do that during refresh instead, so articles are readable offline |
| Custom cache folder | Vault-relative folder holding the caches, with a Migrate cache button beside it |
The list of everything you subscribe to, with Import OPML, Export OPML and Add feed on the toolbar, a search box that filters by title, group or address, and one row per feed showing its title, its group and three actions: refresh, edit and delete.
| Setting | Description |
|---|---|
| Open the note after saving | Open the created note in a new tab |
| Note folder | Destination folder for saved articles; created when missing |
| File name template | Template for the note file name |
| Front matter template | YAML written between the --- markers at the top of every saved note |
| Note body template | Wrapper around the article body |
Shows the version, a Check for updates button that opens the plugin's page in Obsidian, and Reset settings, which restores every option on the page to its default while keeping your subscriptions.
Both the file name and the two templates accept these variables:
| Variable | Value |
|---|---|
{{title}} |
Article title |
{{feed}} |
Feed title, falling back to its address |
{{group}} |
Group the feed is filed under |
{{author}} |
Article author |
{{link}} |
Article address |
{{published}} |
Publication date, YYYY-MM-DD by default |
{{created}} |
Date the note is written, YYYY-MM-DD by default |
{{summary}} |
Summary as shipped by the feed |
{{content}} |
The article body, converted to Markdown — body template only |
A date variable takes a Moment.js format after a colon, for example {{published:YYYY-MM-DD HH:mm}}. Appending :yaml escapes a value for the front matter, which is how the default front matter template writes {{title:yaml}} and {{author:yaml}}. The default file name template is {{published:YYYY-MM-DD}} {{title}}, and the default note folder is RSS Inbox.
data.json..signal-rss/ at the root of your vault, one JSON file per feed — a dot folder, so it stays out of the file explorer and out of Obsidian's index. The folder is configurable; changing it does not move anything by itself, and Settings → Basic settings → Custom cache folder → Migrate cache is the deliberate second step that pulls the caches across from the folder previously in use, merging rather than overwriting so no read flag or extracted body is lost.<config folder>/plugins/<plugin id>/cache/, with .obsidian as the usual config folder). If you have been using the plugin since before it was renamed from rss-subscribe to signal-rss, a cache left behind under the old plugin id needs to be moved into the current folder by hand.This plugin makes network requests, but only to the addresses you subscribe to:
There is no account, no telemetry, no analytics and no third-party service. Requests go through Obsidian's requestUrl API, so they are not sent to any proxy of ours.
Some addresses are unreachable, rate-limited, or hang on the server side. Signal RSS gives up after the configured request timeout and reports the reason rather than waiting indefinitely, and it stops early when the host itself is unreachable instead of retrying every guessed path.
A public RSS bridge (such as rsshub.app or one of the community instances) is often the culprit rather than the site you actually want:
To confirm a feed works before subscribing, open its URL in a browser. A working feed shows raw XML; a broken one shows an error, a blank page, or spins forever.
Feed content is third-party markup and is treated as untrusted:
on* handlers, style, and any javascript: URL are dropped by construction.sanitizeHTMLToDom; innerHTML is not used anywhere in this plugin.dataviewjs or templater fence into a saved note and get it executed later.The plugin works on mobile (isDesktopOnly: false). It uses only cross-platform Obsidian APIs — no Node.js file system access.
The layout adapts to a phone rather than merely fitting on it:
Requires Node.js 18 or newer (CI uses 22) and Obsidian 1.7.2 or newer.
npm install
npm run dev # watch build
npm run build # type check + production bundle
Copy main.js, manifest.json and styles.css into a test vault's .obsidian/plugins/signal-rss/ to try a build.
| Path | Contents |
|---|---|
src/main.ts |
Plugin entry: commands, view registration, shared list state, refresh and feed handling |
src/settings.ts |
The tabbed settings page |
src/i18n/index.ts |
Every user-visible string, in en and zh-cn |
src/core/ |
Feed parsing, fetching, error normalisation, full text extraction, OPML, HTML sanitising, the cache store |
src/view/ |
The sidebar list pane and the reader tab |
src/note/ |
Template rendering and note writing |
src/ui/ |
The add-feed and group-name dialogs |
styles.css |
Plugin styles, using Obsidian's CSS variables |
Every user-visible string goes through t(); a literal is not allowed to reach the UI directly.
manifest.json, package.json and versions.json must carry the same version before anything ships:
node .github/scripts/check-version.mjs # version consistency + manifest rules
node .github/scripts/check-version.mjs --tag X # also assert a tag matches the manifest
CI runs this plus a type check, a production build and a scan of main.js for the dynamic-execution APIs Obsidian's review rejects.
Releases are driven by GitHub Actions and the built assets are attached by CI, since main.js is not tracked:
| Workflow | Trigger | Result |
|---|---|---|
ci.yml |
push to main, any pull request |
type check, build, security scan, asset check |
version-check.yml |
push, tags, pull requests | version and manifest consistency |
release.yml |
pushing a tag, or a manual run | GitHub release with main.js, manifest.json, styles.css |
beta.yml |
commit on main whose subject starts with [beta] or [rc] |
pre-release tagged <version>-beta.N for BRAT |
Bump the version in all three files, commit, then push a tag without a v prefix:
git tag 0.9.0 && git push origin 0.9.0
The same release can be cut from the Actions tab instead, by running Release and passing the version — the workflow creates the tag itself. For testing before a release, start the commit subject on main with [beta], or with [rc] to also clear older pre-releases, then install the resulting pre-release through BRAT. The keywords only count at the very start of the subject line, so a commit that merely mentions them in its body will not cut a pre-release.
MIT