One click to flip any Substack post between the publication webpage and the built-in reader (audio player) — in both directions.
A tiny cross-browser extension that adds a floating pill button in the corner of every Substack post:
| You are on… | Button says | Takes you to… |
|---|---|---|
A publication's webpage (example.substack.com/p/… or a custom domain like thefp.com/p/…) |
🎧 Switch to Post | The built-in reader at substack.com/home/post/p-{id} — with Substack's audio player |
The built-in reader on substack.com (/home/post/p-{id}, /@user/p-{id}, /inbox/post/{id}) |
🌐 Switch to Webpage | The post's original publication webpage |
No account, no permission dialogs, no data collection, no tracking. It only reads the current page and, when needed, calls Substack's own public API.
⬇ Download the latest release ZIP (under Assets, grab substack-reader-switcher-*.zip), then extract it — you get a folder with the extension files. No git or command line needed.
- Open
chrome://extensions(oredge://extensions, etc.). - Enable Developer mode (toggle in the corner).
- Click Load unpacked and select the extracted folder.
Don't delete the folder afterwards — the browser loads the extension from it.
Temporary install (resets when Firefox restarts):
- Open
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on… and select
manifest.jsoninside the extracted folder.
For a permanent install the extension must be signed — zip the folder contents and submit it to addons.mozilla.org (self-distribution signing is free), or use Firefox Developer Edition with xpinstall.signatures.required set to false.
Note
In Firefox, MV3 extensions don't get site access automatically. After installing, open the extension's entry in about:addons → Permissions and allow access to the sites you read Substack on (or allow on all sites).
Safari can't load web extensions directly; convert with Apple's tool on macOS:
xcrun safari-web-extension-converter "path/to/extracted/folder"Then run the generated Xcode project and enable the extension in Safari's settings. The code itself needs no changes.
- Webpage → reader: the post's numeric ID is extracted from the page (app-link meta tags, the
__NEXT_DATA__blob, or inline scripts), falling back to the publication's own/api/v1/posts/{slug}endpoint. The button links tohttps://substack.com/home/post/p-{id}. - Reader → webpage: the post ID is parsed from the URL, and the original article URL comes from
https://substack.com/api/v1/posts/by-id/{id}(post.canonical_url), with the server-rendered<link rel="canonical">used as a no-network fast path when it's trustworthy. - SPA-aware: substack.com is a single-page app, so the script watches for client-side URL changes and adds/removes/updates the button accordingly.
It's a pure content script — no background worker, no chrome.* APIs, no stored data — which is what keeps it compatible across every browser with a single codebase.
| File | Purpose |
|---|---|
manifest.json |
MV3 manifest (content script registration, Firefox settings) |
content.js |
All logic: page detection, post-ID/URL resolution, button injection |
styles.css |
Floating button styling |
Example Links.txt |
Sample post/reader URL pairs for manual testing |
CLAUDE.md |
Developer/AI-agent context notes |
- Substack posts on custom domains are matched via the broad
*://*/p/*pattern; the script immediately exits on non-Substack pages, but the content script does load there. - If Substack changes its URL formats or API responses, detection may need updating (see CLAUDE.md for the current assumptions).