A personal bookmark server, plus the Firefox extension that feeds it. Runs on your own machine, works on desktop and Android, and keeps its own copy of every preview image so your library never goes blank.
┌─────────────────┐ ┌──────────────────────────────┐
│ Firefox │ │ Dogear server │
│ desktop/mobile │──save─▶│ FastAPI + SQLite │
│ (extension) │ │ fetches metadata │
└─────────────────┘ │ caches previews locally │
│ serves the web library │
┌─────────────────┐ │ │
│ Any browser │◀──────▶│ http://your-machine:8000 │
│ (web library) │ └──────────────────────────────┘
└─────────────────┘
git clone https://github.com/Suman2023/dogear.git dogear
cd dogear
./scripts/install.shThat creates a virtual environment, installs the dependencies, writes a systemd unit, and starts the service. It survives reboots and restarts itself if it crashes. It prints the address to use from your phone.
./scripts/install.sh --port 9000 # different port
./scripts/install.sh --user # per-user service, no root
./scripts/install.sh --host 127.0.0.1 # this machine onlyRequires Python 3.10+ and systemd. Nothing else.
./scripts/uninstall.sh # stops and removes the service, keeps bookmarks
./scripts/uninstall.sh --purge # also deletes the database and cached images./scripts/dev.sh # auto-reloads on edit, http://127.0.0.1:8000
./scripts/dev.sh --lan # also reachable from your phone
python3 scripts/seed.py # fill it with sample bookmarks to click around./scripts/deploy.sh john@<pi-ip> --setup-keys # once, stops password prompts
./scripts/deploy.sh john@<pi-ip> --with-data --installCopies the project over rsync (skipping the venv, caches and build
output), then runs install.sh there. --with-data brings your existing
bookmarks and cached previews across. Everything shares one SSH
connection, so you are asked for a password at most once.
./scripts/deploy.sh john@<pi-ip> --install # push an update
./scripts/deploy.sh john@<pi-ip> --restart # just restart the serviceabout:debugging → This Firefox → Load Temporary Add-on → pick
extension/manifest.json. It stays until Firefox closes.
Then open its settings, enter your server address (the one
install.sh printed), and press Save and connect. Firefox will ask
permission to talk to that address.
extension/STORE.md has the whole process — every listing field written
out ready to paste, the reviewer notes, the Android compatibility steps,
and the screenshot list.
The extension doesn't know or care that server/ exists — it only
speaks the HTTP contract in API.md. Point it at any server
that implements those endpoints and it works the same way.
./scripts/package-extension.sh # builds dist/dogear-<version>.zip and lints itThe extension currently passes web-ext lint with 0 errors, 0 warnings,
0 notices, uses no bundler or minifier (so AMO needs no source
submission), and declares Android support.
- One click saves the page, with title, description, preview image and favicon already filled in.
- The toolbar icon shows a checkmark when the page is already saved, so you don't file the same thing twice. Click it again to update or remove.
- Tag while saving, with suggestions drawn from tags you already use.
- Select text on a page and save it as the bookmark's note.
- Right-click any link to save it without opening it.
- Alt+Shift+S saves silently; Alt+Shift+D opens the popup.
- Star a page in Firefox and it lands on your server too, tagged with the folder you filed it under. Switch off in settings if you don't want it.
- Push all bookmarks in settings sends your entire existing Firefox library across in one go, folders becoming tags, with a progress bar. It skips anything already there, so it is safe to run twice.
- Grid, list, and compact layouts.
- Full-text search across titles, descriptions, notes, tags and URLs, using SQLite FTS5 with prefix matching — results appear as you type.
- Filter by tag, by site, or by favourites / unvisited / untagged / archive / broken links. Filters live in the URL, so any view can be bookmarked itself.
- Select several cards for bulk tagging, archiving or deletion.
- A monospace "Console" interface: hairline rules, no shadows or blur, numbered rows.
- Appearance settings are stored on the server, so the theme, accent
colour and default layout are the same on every device you open it
from. Dark by default, with a light "paper terminal" alternative and six
accent schemes.
?theme=and?layout=override per link. - Every bookmark records which browser and device saved it —
Zen (Linux),Firefox (Android)— so you can tell where something came from. - Installable as a web app on Android, and it still opens offline.
- Deleting shows an Undo for eight seconds.
This is the part that most bookmark tools get wrong, and the reason the old version showed blank cards:
- The extension reads what the page publishes, handling both
property="og:image"and the widespreadname="og:image"spelling. - The server independently fetches the page and parses it again, so pages the extension couldn't read still get a preview.
- If there is no card metadata at all, the server looks for a suitable image in the page body, ignoring logos, sprites and tracking pixels.
- Sites that refuse anonymous requests (Wikipedia among them) are retried once with a user agent that identifies Dogear honestly.
- Every preview and favicon is downloaded and stored locally. Hotlink protection, expiring CDN URLs and deleted images cannot break your library afterwards.
- Anything still missing can be filled in later with Rebuild previews under Import & export.
Against a sample of twenty real sites this yields previews for 14, and favicons and descriptions for essentially all of them.
- Export as JSON, or as a standard browser bookmark file that imports straight back into Firefox, Chrome or Safari.
- Import a browser export (folders become tags), a Dogear JSON file, or a plain list of URLs. Duplicates are detected and skipped.
- Check for dead links flags bookmarks whose pages no longer respond, collected under a Broken links view.
- URLs are normalised before saving — tracking parameters stripped,
www.and trailing slashes ignored — so the same page saved twice stays one bookmark. - Reading time is estimated from the page's word count.
dogear/
├── scripts/
│ ├── install.sh systemd service, starts on boot
│ ├── uninstall.sh removes it; --purge deletes data too
│ ├── dev.sh local server with auto-reload
│ ├── seed.py sample bookmarks for testing
│ ├── deploy.sh copy to a Pi and install it there
│ ├── make-icons.py regenerates every PNG icon
│ └── package-extension.sh builds and lints the AMO zip
├── server/
│ ├── app/
│ │ ├── main.py HTTP API
│ │ ├── db.py schema, migrations, search index
│ │ ├── enrich.py page fetching, metadata, image cache
│ │ ├── urls.py normalisation and deduplication
│ │ ├── transfer.py import/export
│ │ ├── models.py request/response shapes
│ │ └── config.py environment settings
│ ├── static/ the web library (no build step)
│ ├── tests/ 77 tests
│ └── data/ your database and cached images
└── extension/
├── manifest.json MV3, desktop + Android
├── background.js metadata, icon state, sync, menus
├── lib/dogear.js shared API client
├── popup/ the save panel
├── options/ settings and bulk push
└── STORE.md how to publish on AMO
Your data lives in server/data/ — one SQLite file and a folder of cached
images. Back it up by copying that directory.
cd server
.venv/bin/python -m pytest tests/ -q77 tests covering the API, URL normalisation and deduplication, metadata extraction, import/export round trips, and — importantly — upgrading an existing v2 database without losing bookmarks. They run offline; no test touches the network.
Everything has a working default. Override with environment variables in the systemd unit if you need to:
| Variable | Default | Meaning |
|---|---|---|
BOOKMARKS_DATA_DIR |
server/data |
Where the database and images live |
BOOKMARKS_API_KEY |
auto-generated | The shared key; see "A note on access" |
BOOKMARKS_SESSION_DAYS |
90 |
How long a web login stays signed in |
BOOKMARKS_FETCH_TIMEOUT |
15 |
Seconds to wait for a page |
BOOKMARKS_ENRICH_CONCURRENCY |
4 |
Pages fetched at once |
BOOKMARKS_MAX_IMAGE_BYTES |
8000000 |
Largest preview to cache |
BOOKMARKS_USER_AGENT |
a Chrome string | Used when fetching pages |
Dogear is protected by a single shared key, BOOKMARKS_API_KEY. If you
don't set one, the server generates a random one on first run and keeps it
in server/data/.api_key — read it with cat server/data/.api_key.
- The web library exchanges the key for a session cookie at a login screen the first time you open it, and stays signed in from then on.
- The extension needs the key pasted into its settings, next to the server address.
- A bookmarklet or share-sheet shortcut that can't set a header can
instead append
?api_key=...to the request URL — e.g.https://your-server/api/bookmarks?api_key=...for a POST body it can't otherwise sign. This is weaker than the other two: the key ends up in browser history and in the server's access log, so only use it where a header genuinely isn't an option, and rotate the key if such a URL ever leaks.
Rotate it at any time by deleting server/data/.api_key (and
.session_secret, to sign everyone out) and restarting the service, or by
setting BOOKMARKS_API_KEY yourself in the systemd unit.
This is still a single shared secret, not a full account system — treat it like a password. It's what makes it reasonable to expose Dogear through a tunnel (Cloudflare Tunnel, Tailscale Funnel) or a reverse proxy; for anything more sensitive, put it behind a VPN such as Tailscale instead.
Mozilla Public License 2.0 — the same licence Firefox itself uses. Use it, modify it, ship it inside something larger; changes to the files themselves stay under the MPL.
PRIVACY.md is the privacy policy, reproduced on the
add-on's listing page. Nothing here collects or transmits anything to
anyone but the server you run.