Skip to content

mkoester/Bookmarks-plus

Repository files navigation

Bookmarks+ (Bookmarks-plus)

Browser extension for Linkding — displays your bookmarks as a customizable folder-based launcher on the New Tab page and popup.

Repository renamed from linkding-ext to Bookmarks-plus on 2026-07-05 (GitHub redirects the old URLs).

Features

  • New Tab page with configurable bookmark folders (on Firefox the launcher is also set as your homepage, so new windows and the Home button show it too)
  • Popup for quick access
  • Folder rules: match bookmarks by tag, URL, title, or source provider with nested ALL/ANY/NONE groups (e.g. A AND (B OR C), or "everything except…"); optional per-folder "latest N" limit; rules editable as JSON per folder, and all folder definitions exportable/importable as JSON. Reorder the conditions in a rule by dragging
  • Per-folder display order: sort by newest-added, recently-modified, or alphabetical; give the conditions of an OR (ANY) rule a weight to rank their matches higher
  • Open every bookmark in a folder in background tabs with one button, or open a single bookmark in the background — works on trackpads and touch, not just middle-click; available on all surfaces including the New Tab page (which can optionally close itself after opening a whole folder)
  • Reorder whole folders by dragging, same as rule conditions
  • Background sync on timer + on New Tab open, plus a per-provider "Sync now" button. Each provider can override the global sync interval on its own settings tab. Syncs are incremental where the source supports it — Linkding via modified_since, web feeds via HTTP conditional GET (ETag/Last-Modified) — with a periodic full sync (configurable, default every 24 h) to catch deletions, which partial updates can't see; the Linkding tab also has a "Full sync now" button to reconcile deletions immediately
  • Import your browser's own bookmarks (folder names become tags)
  • Subscribe to web feeds — RSS 2.0/1.0, Atom, or JSON Feed, auto-detected — a feed's current items appear as bookmarks (categories become tags, linkblog external_url supported), pair with a provider folder rule for a "latest from X" folder
  • Optional remote folder source: load all folder definitions from a JSON file on any web server (a raw GitHub/gist URL, your own host) — the file is the single source of truth, refreshed via "Sync folders now" buttons (options page and every surface, next to the Settings button) or on an opt-in interval; see below
  • Static demo data mode (no Linkding instance needed)
  • Firefox + Chrome from one codebase

Development

pnpm install

# Chrome (watch mode)
pnpm run dev:chrome

# Firefox (watch mode)
pnpm run dev:firefox

Output lands in dist/<target>/.

Build targets

There are three targets, differing only in their manifest:

Target Output New Tab override? Name
firefox dist/firefox/ yes (Firefox lets the user toggle it in Settings → Home) Bookmarks+
chrome dist/chrome/ no — leaves Chromium's native new tab alone Bookmarks+
chrome-newtab dist/chrome-newtab/ yes Bookmarks+ (new tab edition)

Why two Chromium builds: a chrome_url_overrides.newtab override is static and can't be toggled at runtime, and Chromium offers no per-extension switch to revert to the native new tab. So Chromium users pick the build they want — plain, or the new-tab edition.

New windows: on Chromium a new window opens the New Tab page, so the chrome new-tab edition already covers it. Firefox instead loads the homepage (about:home) in new windows and on the Home button — a separate surface the new-tab override doesn't touch — so the Firefox build also sets chrome_settings_overrides.homepage to the launcher. Firefox prompts once to allow the homepage change; the user always keeps final control (even against another extension) at about:preferences#home, which has both the Homepage and new windows and New tabs dropdowns. (The homepage key does nothing useful on Chromium, so it's Firefox-only.)

Load in browser

Chrome / Chromium: chrome://extensions → Enable developer mode → Load unpacked → select dist/chrome/ (or dist/chrome-newtab/)

Firefox: about:debugging → This Firefox → Load Temporary Add-on → select dist/firefox/manifest.json

Build

pnpm build                 # all three targets
pnpm run build:firefox
pnpm run build:chrome
pnpm run build:chrome-newtab

Versioning

The version lives in package.json only. It's injected into each browser's manifest.json at build time (webpack.config.ts), so the manifests carry no version field of their own. To release, bump it in one place:

pnpm version patch   # or edit package.json "version"

Project structure

shared/          # All shared logic (types, storage, bookmarks, rendering, validation)
  data/          # Static mock bookmark data
src/
  background/    # Service worker — sync logic, alarms, message handling
  newtab/        # New Tab override page
  popup/         # Toolbar popup
  sidebar/       # Sidebar (Firefox) / side panel (Chrome)
  options/       # Settings + folder rule editor
  onboarding/    # First-run onboarding page
tests/           # Unit tests (node --test), run as part of pnpm build
manifests/       # manifest.shared.json + per-target overlays (firefox, chrome, chrome-newtab)
public/icons/    # Extension icons (icon.svg source + rasterised PNGs)

Configuration

Open the extension options to:

  • Set your Linkding URL and API token (+ optional username, see below)
  • Configure the sync interval
  • Toggle static demo data mode
  • Import browser bookmarks (requests the bookmarks permission)
  • Define bookmark folders with match rules

Linkding connection & permissions

Authentication is token onlyAuthorization: Token <token>. The Linkding REST API does not use the username; the username field is a display-only label and is never sent. (Get a token in Linkding under Settings → Integrations → REST API.)

The username/session of your logged-in browser cannot be reused (unlike the old Tampermonkey userscript): an extension's requests are cross-origin to your Linkding host, so the SameSite=Lax session cookie isn't sent. Token auth is the supported path and works in a fresh profile / background sync regardless of login state.

On Save, the options page requests host permission for exactly your configured Linkding origin (e.g. https://links.example.com/*), bundled with the bookmarks request into a single prompt. This host permission is what lets the extension read the API cross-origin (it grants a CORS bypass for the extension's own fetches). Without it you'd see a CORS header 'Access-Control-Allow-Origin' missing / 401 failure.

Firefox quirk: because the manifest declares the optional host permission as <all_urls>, a grant scoped to a single host is stored and active but does not appear in about:addons → Permissions (the "Access your data for all websites" toggle stays off, and the specific host isn't listed). Confirm it from the extension console with browser.permissions.getAll(). Re-saving an existing Linkding provider after upgrading triggers the host-permission prompt once.

Remote folder source

The Folders tab can point at a JSON file on a web server (same format as the folder export) that then owns the folder definitions — handy for keeping several browsers/machines on one shared, version-controlled folder set:

  • Use a raw file URL (https://raw.githubusercontent.com/... or https://gist.githubusercontent.com/..., not the github.com HTML page); any static web server works. On Save, host permission is requested for exactly that origin — the same mechanism as for Linkding/feed providers above.
  • Every refresh replaces all folders, so local folder editing is disabled while a source is configured (the Folders tab shows them read-only; Export stays available — it's how you seed the file in the first place). Clear the URL and Save to return to local editing.
  • By default the source is only fetched on "Sync folders now" — a button on the Folders tab plus a ⟳ button next to Settings on the popup, sidebar, and New Tab page — and automatically once after Save/URL changes. An optional interval (minutes) enables regular background refreshes.
  • Fetches use HTTP conditional GET (ETag/Last-Modified) plus a content hash, so an unchanged file is a no-op (folder ids and storage don't churn). Fetch/validation errors appear in the usual sync-error banner and the previous folders stay in place.
  • Limitation: provider rule conditions reference this installation's provider config ids, which differ per machine — a folder file shared across machines should stick to tag/URL/title conditions.

Pagination

The Linkding provider fetches all bookmarks by following the API's next link (/api/bookmarks/?limit=100). Note Linkding has no tag query parameter — tag filtering is done via the search query (?q=%23tagname), not ?tag=. The provider doesn't filter by tag, so this doesn't apply here, but it's the gotcha behind tag-filtered fetches elsewhere.

Browser bookmark tags

The browser-bookmarks provider tags each imported bookmark with the names of the folders it lives in — a bookmark under Bookmarks Toolbar / crowdsourcing gets the tags Bookmarks Toolbar and crowdsourcing. To match a folder rule by tag, put the bookmark inside a folder of that name.

Firefox's native bookmark tags (the tag field in the Edit Bookmark dialog) are not readable from an extension — the WebExtension API only exposes the folder structure, so only folder names can become tags.

Icons

The icon source is public/icons/icon.svg. Re-rasterise the PNGs the manifest references with:

rsvg-convert -w 48  public/icons/icon.svg -o public/icons/icon48.png
rsvg-convert -w 128 public/icons/icon.svg -o public/icons/icon128.png

License

MIT © Mirko K. — free for any use, including commercial, with modification and redistribution permitted.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages