Syndicate elsewhere
A simple, local-first publishing dashboard for syndicating blog posts and podcast episodes from RSS feeds to social platforms like Bluesky, Mastodon, Slack, LinkedIn, and Threads.
Overview
Syndicate Elsewhere is a lightweight, single-page HTML/JavaScript app that acts as a manual syndication inbox. It reads one or more RSS feeds, identifies content that has not yet been syndicated, and presents a queue of publishable items. You can review, edit, and publish a generated social post to one or more destinations with a single click.
The application is intentionally local-first, single-user, and manual rather than automated. RSS remains the source of truth — the app only stores references to feed entries plus your publication state. Everything lives in browser localStorage; there is no backend.
Note on Slack: Slack's webhook endpoint does not send CORS headers, so the browser cannot read its response. The app sends the post and treats a successful network request as success — it cannot detect a Slack-side rejection (e.g. a revoked webhook). Use Test Connection to confirm messages arrive.
Note on LinkedIn & Threads: Their APIs cannot be called from a backendless browser app (they block cross-origin requests and require an OAuth token that can't be stored safely client-side). These use assisted posting instead: publishing opens the platform's web composer with your draft prefilled where possible, and the item is marked published once the composer opens — you complete the post there. For Threads the text is prefilled via its web intent; for LinkedIn the share dialog opens for the article and your post text is copied to the clipboard (LinkedIn does not allow prefilling commentary). Allow pop-ups for this site so the composer can open.
Features
- Multiple feeds — add blog and podcast RSS/Atom feeds, validate them, edit and delete them.
- Manual refresh — pull all feeds and build a queue of unpublished items.
- Auto-generated drafts — editable templates per content type with variables like
{title},{url},{shortDescription}. - Composer — edit post text, pick destinations, see a character counter, and publish.
- Per-destination publication tracking — partial publication and retries are supported; an item leaves the queue only once it is published everywhere enabled.
- Skip items you don't want to syndicate.
- Bluesky publishing via ATProto (handle + app password), with link facets and a basic link card.
- Mastodon publishing via the REST API (instance URL + access token).
- Slack publishing via an Incoming Webhook (one webhook per channel), with a rich content card (title, summary, and image) built from the same editable card fields Bluesky uses.
- LinkedIn and Threads via assisted posting — publishing opens the platform's own composer with your draft prefilled (Threads) or copied to the clipboard (LinkedIn). No credentials or OAuth required.
- Record syndication links on GitHub — per feed, after publishing to Bluesky or Mastodon, commits the new post URLs to the item's source front matter in that feed's repository (see below).
- Themes (system / light / dark), queue ordering, and optional 15-minute auto-refresh.
- Export / import your full configuration as JSON.
Usage
- Open
index.htmlin a browser (or runnpm startfrom the repo root). - Go to Settings and enable the destinations you want. Bluesky, Mastodon, and Slack take credentials (use Test Connection to verify); for Slack, create an Incoming Webhook for the target channel and paste its URL. LinkedIn and Threads need no credentials — just enable them (they use assisted posting; see the note above).
- Go to Feeds, add an RSS feed (Name, URL, Type), optionally Validate Feed, then Save Feed.
- Click ↻ Refresh Feeds in the header.
- On the Queue, click Compose & Publish on a card, edit the draft, choose destinations, and Publish Selected.
- Return later and refresh — only content you haven't fully published (and haven't skipped) appears.
CORS proxy
Most RSS feeds do not send CORS headers, so browsers block fetching them directly. Syndicate Elsewhere tries a direct fetch first and falls back to a configurable CORS proxy (set in Settings → General). The Bluesky and Mastodon APIs themselves are called directly and support CORS.
The default proxy is /rss-proxy?url=, served by the bundled Netlify edge function at netlify/edge-functions/rss-proxy.ts (the deployed equivalent of a Cloudflare Worker). It works automatically on the deployed Netlify site, or locally when you run netlify dev. The function only proxies http(s) URLs and refuses localhost / private-network targets to avoid being an abusable open proxy.
For other local setups (e.g. opening the file directly or npm start), either run netlify dev, or change the proxy to a public one such as https://api.allorigins.win/raw?url=, or clear it to disable the fallback.
Recording syndication links
If a feed's site source is on GitHub, the app can record where each of its items was shared. After Publish Selected succeeds on Bluesky and/or Mastodon, it adds the new post URLs to the item's source file front matter in one commit:
syndication:
- https://bsky.app/profile/you.example/post/3l…
- https://mastodon.example/@you/1131…
Your site can use these to invite responses on each platform and to match backfed webmentions (e.g. from Bridgy).
It's set up per feed, so each feed records to its own repository: open the feed on the Feeds tab and expand Record syndication links on GitHub:
- Repository and Branch — where that site's source lives, e.g.
owner/siteonmain. - Source path — how an item's link maps to its source file.
{path}is the link's path, so withsrc/{path}.post,https://example.com/2026/10/02/slug→src/2026/10/02/slug.post. Items with no file at that path (e.g. a link to another site) are skipped with a warning. - Access Token — a fine-grained personal access token limited to that repository, with Contents: Read and write permission and an expiry date. Use Test GitHub to check it can write to the branch.
Feeds without a repository (e.g. a podcast) don't record anything.
The front matter is edited as text, so nothing else in it changes, and links already listed aren't added again. Bluesky links use your handle rather than your DID. If the file changed in between reading and writing, the commit is retried once. If recording fails (say, an expired token), the item stays in the queue marked Links not recorded with a Record links button to retry.
Each commit lands on the branch directly, so pull before pushing your own changes to the site.
Templates
Each content type has an editable template. Available variables:
{title} · {url} · {summary} · {shortDescription} · {description} · {author} · {feedName} · {publishedDate}
Defaults:
Blog: New post: {title}\n\n{url}
Podcast: New episode: {title}\n\n{shortDescription}\n\nListen: {url}
Data & Security
All state — feeds, templates, credentials, and publication history — is stored under the syndicateElsewhere key in browser localStorage. Credentials (Bluesky app password, Mastodon token, Slack webhook URL, per-feed GitHub tokens) are stored in plain text locally, which is acceptable for a single-user, local-first tool with no shared backend. LinkedIn and Threads store no credentials at all (assisted posting). Use app passwords / scoped tokens, treat the Slack webhook URL as a secret, and use Reset All Data to wipe everything. GitHub tokens are left out of Export backups (importing a backup keeps the tokens already in the browser), since they can write to a repository. Bear in mind that every tool served from the same site origin can read this localStorage.
Technical Details
- Pure HTML/CSS/vanilla JavaScript — no frameworks, no build step.
index.html(markup + styles) andapp.js(logic).- RSS 2.0 and Atom parsing via the browser
DOMParser. - Bluesky via ATProto XRPC; Mastodon via REST; Slack via Incoming Webhook; LinkedIn & Threads via assisted posting (web composer / share intent); GitHub via the REST contents API.
Browser Compatibility
Works in modern Chrome, Firefox, Safari, and Edge.
Roadmap
Planned future enhancements include image/OpenGraph support, platform-specific drafts and preview cards, scheduling, batch publishing, AI-assisted drafts, and additional destinations (Discord, Ghost, Micro.blog, etc.). Slack support could be extended with Block Kit formatting and per-post channel overrides (it currently posts the body plus a content-card attachment); LinkedIn and Threads could gain true one-click API posting if a small backend/OAuth relay is added (they currently use assisted posting).
License
See repository root for license information.