A minimal, self-hosted online notepad. Open a URL, start typing — your text is saved automatically and synced to any tab pointing at the same URL.
Demo Note: The data of the demo will be wiped on an ad-hoc basis
Bun server · Vite + TypeScript frontend · SQLite storage · ships as a single static binary in a small Docker image.
The
ktor-BEbranch runs the same features on Ktor/Kotlin. Feature parity, same wire protocol, same session cookie contract — only the backend implementation differs.
Editing & sync
- URL is the identity — visit
/anythingand you have a note. - Live sync over WebSocket, debounced per keystroke, multi-tab safe.
- Auto-reconnect with exponential back-off; unsent edits are buffered and flushed.
Cmd/Ctrl-Sforces an immediate flush.- 20 MB max content size per note.
- Empty notes are not persisted; deleting all content removes the row.
Presentation
- Light / Dark theme (Solarized-dark variant).
- Font family (15+ mono / sans / serif), font size (12–48 px), line wrapping, line numbers.
- All view settings saved per note in
localStorage(LRU-capped at 200).
Sharing
- Password protect / change / remove per note.
- Read-only share link, one-click copy.
- View as raw text or rendered Markdown (with highlight.js syntax highlighting).
Admin page (/admin)
- Opt-in via
NOTEPAD_ADMIN_PWenv var. Disabled by default. - Login-gated table of all notes, sortable by size / updated time.
- Per-row actions (Gmail-style hover): view content, open in new tab, set/remove password, delete.
- Bulk-select + bulk-delete.
- Rate-limited login; admin session auto-expires after 8 h.
Hardening
- Signed session cookies (
SameSite=Strict,HttpOnly,Securein prod). - WebSocket bound to your own note key — cross-note writes blocked.
- Rate limits on unlock, admin login, and writes.
- Refuses to start if
SESSION_SIGN_KEYis unset, too short, or equals the known-leaked default from earlier in the repo history.
- Docker (or Podman) — nothing else on the host.
| Env var | Required | Purpose |
|---|---|---|
SESSION_SIGN_KEY |
Yes | Random hex ≥ 32 chars for signing session cookies. Generate: openssl rand -hex 32. Refuses to start without. |
NOTEPAD_ADMIN_PW |
No (unset → admin disabled) | Plain-text password for the /admin page. Leave unset to hide/disable the admin feature. |
DB_PATH |
No (/data/note.db in Docker) |
SQLite file path. |
PORT |
No (8080) |
TCP port to listen on. |
NODE_ENV |
No (production in Docker) |
Set to production to enable the Secure cookie flag (requires HTTPS). |
STATIC_ROOT |
No (auto-detected) | Override the frontend build directory. Defaults to frontend/dist next to the source or the executable. |
docker run -d --name notepad \
-p 8080:8080 \
-v $HOME/notepad-data:/data \
-e SESSION_SIGN_KEY=$(openssl rand -hex 32) \
-e NOTEPAD_ADMIN_PW=change-me \
notepad:latestNotepad at http://localhost:8080, admin at http://localhost:8080/admin.
services:
notepad:
image: notepad:latest
restart: unless-stopped
ports: ["8080:8080"]
volumes:
- ./notepad-data:/data
environment:
SESSION_SIGN_KEY: ${SESSION_SIGN_KEY:?run: openssl rand -hex 32}
NOTEPAD_ADMIN_PW: ${NOTEPAD_ADMIN_PW:-}docker build -t notepad:local .cd frontend && bun install && bun run build && cd ..
SESSION_SIGN_KEY=$(openssl rand -hex 32) bun server/index.tsOr bun run dev for hot reload.
MIT — see LICENSE.