Can't decide what to read? Click Surprise Me and land on a random article from a curated catalog. No scrolling, no feed, no overthinking. Just chance.
A distraction-free browser extension that opens random articles from curated feeds.
Note: The UI is intentionally minimal because the point is getting you to an article fast. If something looks off, it's a bug; file an issue.
- Surprise Me opens a random article from your enabled sources in a new tab (or the current one)
- Keyboard shortcut (
Ctrl+., macOSCmd+.) rolls without opening the popup; remap it underchrome://extensions/shortcuts - Snooze sources hides a source from rolls for a day, a week, or a month without removing it
- Per-source max age overrides the global article-age filter for individual sources
- Tag filtering includes or excludes categories (
technology,web,security, ...) to narrow the pool - Selection pool picks from unread only, everything, or starred articles; keyword include/exclude filters by title
- Reading history tracks every article opened via Surprise Me, exportable as CSV/JSON
- Online catalog ships from this repo via GitHub raw and is checked every 6 hours, preserving your source toggles on update. Import your own
catalog.json(file picker or drag-and-drop) or point at your own URL. - Background refresh fetches feeds automatically on a configurable interval (30 minutes to 24 hours). The stored pool is capped at 7 MB; oldest unstarred articles drop first.
- Light and dark themes with an accent color that stays out of the way
- Manifest V3 WebExtension
- TypeScript
- Vite + CRXJS
- Lit for UI
- fast-xml-parser for RSS / Atom / sitemap parsing
- Zod for runtime schema validation
- Biome for linting and formatting
browser.storage.localfor persistence,chrome.alarmsfor background refresh
- Node.js 22+
- pnpm v11 (install with
npm i -g pnpm@11)
pnpm install --frozen-lockfile
pnpm buildThe built extension lands in dist/chrome/.
pnpm build:firefoxOutputs to dist/firefox/ with a browser_specific_settings.gecko manifest and a background.scripts event page (Firefox doesn't support MV3 background.service_worker).
| Command | Output | Description |
|---|---|---|
pnpm build |
dist/chrome/ + release/random-reader-chrome.zip |
Chrome Web Store package |
pnpm build:firefox |
dist/firefox/ + release/random-reader-firefox.zip + release/random-reader.xpi |
Firefox package (AMO upload is random-reader-firefox.zip) |
The AMO submission package (
release/random-reader-firefox.zip) is a zip of thedist/firefox/directory contents withmanifest.jsonat the archive root.
- Open
about:debugging#/runtime/this-firefox - Click Load Temporary Add-on…
- Select
dist/firefox/manifest.json(Firefox wants themanifest.jsonfile, not the folder) - The extension stays loaded until Firefox restarts
For a permanent install, zip the contents of dist/firefox/ (so manifest.json is at the zip root) and upload to Firefox Add-ons (free, no developer fee), since Firefox blocks unsigned permanent add-ons.
- Open
chrome://extensions - Enable Developer mode (toggle in the top-right)
- Click Load unpacked
- Select the
dist/chrome/folder - Pin the extension and click the toolbar icon
pnpm devRuns the Vite dev server with CRXJS hot-reload.
Note: CRXJS hot-reloads the popup and options pages automatically, but MV3 service workers are not reloaded by HMR. After changing anything under
src/background/, go tochrome://extensionsand click the reload button on the extension. Otherwise you'll be debugging stale service worker code.
| Setting | Options | Default |
|---|---|---|
| Open Articles In | new_tab / current_tab |
new_tab |
| Feed Refresh Interval | 30 minutes to 24 hours | 24 hours |
| Selection Pool | Unread Only / All / Starred Only | Unread Only |
| Article Discovery | Recent posts / Deep archive | Recent posts |
| Max Article Age | All time up to 3 months | All time |
| Include / Exclude Categories | any catalog tag | none |
| Keywords | include / exclude by title | none |
Sources live in catalog.json (repo root) and are served from the repo itself (https://raw.githubusercontent.com/GrishMahat/RandomReader/refs/heads/main/catalog.json) as the default remote catalogUrl, so every user shares the same curated list. Each source has:
{
"id": "hn-frontpage",
"name": "Hacker News",
"type": "rss",
"url": "https://news.ycombinator.com/rss",
"language": "en",
"enabled": true,
"tags": ["technology", "tech", "news"]
}typeis the feed format:rss,atom, orsitemap.include/exclude(optional) are path-prefix filters applied to the article URL.archive(optional) declares paginated history for Deep archive discovery, e.g."archive": { "template": "https://example.com/feed/?paged={n}", "wpTotalPages": true }. WithwpTotalPages: truethe exact depth is discovered live from the WordPress REST API (X-WP-TotalPages) and cached per source, so no depth numbers live in the catalog. Without the flag,maxPagesis the static depth; an audit of the catalog found ~34% of feeds respond to one of the pagination conventions.
include/exclude matter mainly for sitemap sources, which list every URL on a site, landing and category pages included. Path filters keep only the real articles (for example "/about/news/" keeps news posts while "/archive/" drops archive pages).
RSS and Atom feeds generally link straight to an article, so they usually need no filtering. Add exclude only if a feed's links redirect to the blog homepage or some other wrong page:
{
"id": "example-feed",
"name": "Example Feed",
"type": "rss",
"url": "https://example.com/rss",
"enabled": true,
"tags": ["web"],
"exclude": ["/redirect/", "/home"]
}The bundled catalog ships with 130+ verified sources across tech, web, security, science, and maker niches.
You can import your own catalog via the Catalog section in Options (drag-and-drop a .json file) or set a remote catalogUrl and sync. The extension checks the online catalog every 6 hours and applies updates, keeping your enabled/disabled source toggles, snoozes, and blocked domains intact.
| Command | Description |
|---|---|
pnpm dev |
Vite dev server with HMR (reload the service worker manually from chrome://extensions) |
pnpm build |
Type-check then build to dist/chrome/ |
pnpm build:firefox |
Type-check then build to dist/firefox/ |
pnpm preview |
Preview the build |
pnpm lint |
Type-check (tsc --noEmit) + Biome lint/format check |
pnpm format |
Apply Biome formatting to the whole codebase |
catalog.json # Source catalog (served via GitHub raw as the default online catalog)
manifest.config.ts # Shared extension manifest (CRXJS)
vite.config.ts # Vite/CRXJS build config, per-browser manifest, release zips
biome.json # Linter & formatter config
src/
├── background/ # Service worker
│ ├── main.ts # Alarm scheduling + message handler registry
│ ├── feeds.ts # Pool CRUD, roll tracking, batch refresh, random selection
│ ├── random.ts # Open-a-random-article flow (tab handling, streaks)
│ ├── catalog.ts # Catalog load/import/validate/update
│ └── store.ts # Single typed seam over chrome.storage.local
├── providers/ # RSS / Atom / sitemap parsers
├── models/ # Zod schemas, message types, response map
├── utils/ # Messaging, icons, theme, shared settings vocabulary
├── config/ # Interest groups for onboarding
├── popup/ # Popup UI (Lit)
├── options/ # Options page (Lit)
└── icons/ # Extension icons & logo
Released under the GNU General Public License v3.0. See LICENSE for details.
This is free software: you can redistribute it and/or modify it under the terms of the GPL. If you distribute a modified version, you must make the source available under the same license.