Bookmarks is a self-hosted, single-user bookmarking app inspired by the classic del.icio.us bookmark service, built for saving links, organizing them with categories and tags, and browsing them in a clean feed.
It can save simple bookmarks, embed bookmark-only YouTube links, enrich website bookmarks with Open Graph previews or screenshots, and download media with an internal yt-dlp downloader.
Built by Ketah with assistance from OpenAI Codex.
Bookmarks is for people who want a personal, self-hosted feed of things they care about: articles, videos, posts, music, tutorials, and watch-later links. It keeps the app intentionally simple: one trusted user, local SQLite storage, local media files, and a browser extension for quick saving.
- Single-user web UI with username/password login.
- API token authentication for browser extension/API use.
- Public/private bookmark visibility.
- Readonly public feed at
/. - Authenticated management feed at
/bookmarks. - Categories and tags.
- Exact category and tag filtering.
- To Watch workflow with a
Watchedaction. - Bookmark-only YouTube embeds.
- Website previews from Open Graph metadata or local screenshots.
- Optional media download through
yt-dlp. - Local media storage under
/srv/webdata/bookmarks. - JSON export/import.
- Admin bulk edit page.
- Label management page.
- App status page with storage charts.
- Chrome/Brave-compatible Manifest V3 extension.
This project is currently designed for one trusted self-hosted user. It is not a multi-user SaaS app.
Media download support is built in through yt-dlp and ffmpeg. ReClip-compatible downloads are still available as an optional fallback by setting DOWNLOADER_BACKEND=reclip.
Clone the repository:
git clone https://github.com/erille/Bookmarks.git
cd BookmarksCreate your environment file:
cp .env.example .env
nano .envAt minimum, set:
BOOKMARKS_USERNAME=admin
BOOKMARKS_PASSWORD_HASH=<argon2id password hash>
BOOKMARKS_API_TOKEN=<long random token>
SESSION_SECRET_KEY=<long random secret>
For local HTTP testing, also set:
SESSION_COOKIE_SECURE=false
APP_BASE_URL=http://localhost:8015
Generate random secrets:
openssl rand -hex 32Generate an Argon2 password hash:
python -m venv .venv
. .venv/bin/activate
pip install -r backend/requirements.txt
python -c "from argon2 import PasswordHasher; import getpass; print(PasswordHasher().hash(getpass.getpass('Password: ')))"Argon2 hashes contain $ characters. In .env, wrap the generated hash in single quotes:
BOOKMARKS_PASSWORD_HASH='$argon2id$v=19$m=65536,t=3,p=4$...'
Start the app:
docker compose up -d --buildOpen:
http://localhost:8015/login
By default, Docker Compose mounts persistent data here:
/srv/webdata/bookmarks
Expected layout:
/srv/webdata/bookmarks/
data/bookmarks.sqlite
media/videos/
media/images/
media/audio/
media/thumbnails/
media/previews/
media/tmp/
logs/
Create it manually if needed:
sudo mkdir -p /srv/webdata/bookmarks/{data,media/videos,media/images,media/audio,media/thumbnails,media/previews,media/tmp,logs}
sudo chown -R $USER:$USER /srv/webdata/bookmarks
chmod 750 /srv/webdata/bookmarksBy default, Bookmarks downloads media internally:
DOWNLOADER_BACKEND=internal
The internal downloader uses yt-dlp and ffmpeg, both installed in the Docker image. The default format prefers MP4-compatible media up to 720p:
YTDLP_FORMAT=bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best[height<=720]/best
To use an existing ReClip-compatible service instead:
DOWNLOADER_BACKEND=reclip
When Bookmarks runs in Docker and ReClip runs on the Docker host:
RECLIP_BASE_URL=http://host.docker.internal:8899
When Bookmarks and ReClip run on the same host without Docker isolation:
RECLIP_BASE_URL=http://127.0.0.1:8899
Bookmark-only saves do not use the downloader.
The extension lives in extension/.
To load it in Chrome or Brave:
- Open
chrome://extensionsorbrave://extensions. - Enable Developer mode.
- Click
Load unpacked. - Select the
extension/directory. - Open extension options.
- Set your Bookmarks base URL and API token.
Default local base URL:
http://localhost:8015
The extension manifest allows HTTP and HTTPS self-hosted URLs so it can work with custom domains. If you want stricter browser permission prompts, edit extension/manifest.json and restrict host_permissions to your own Bookmarks URL before loading it.
Health check:
curl -s http://localhost:8015/healthView logs:
docker compose logs -f bookmarksDry-run orphan media cleanup:
docker exec bookmarks python -m app.cleanup --dry-runDelete orphan media after reviewing the dry run:
docker exec bookmarks python -m app.cleanup --deleteExport metadata:
curl -s http://localhost:8015/api/export \
-H "Authorization: Bearer $BOOKMARKS_API_TOKEN" \
-o bookmarks-export.jsonImport metadata:
curl -s -X POST "http://localhost:8015/api/import?overwrite=false" \
-H "Authorization: Bearer $BOOKMARKS_API_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @bookmarks-export.jsonBack up:
/srv/webdata/bookmarks/data
/srv/webdata/bookmarks/media
/srv/webdata/bookmarks/.env
The JSON export stores metadata only. It does not contain video/image/preview files.
- FastAPI
- SQLite
- SQLAlchemy
- Jinja2
- Vanilla JavaScript
- yt-dlp
- ffmpeg
- Docker Compose
- Chrome Manifest V3 extension
- Improve download queue/progress details.
- DB-backed setup/bootstrap flow.
- Admin-managed API tokens.
- Favorite/star bookmarks.
- Archive/hide bookmarks.
- Mobile/PWA share target.
- Optional multi-user mode.
MIT




