Offline Notepad is a minimal, offline-first notebook that securely syncs across browsers and devices.
Offline-first. Notes and folders are saved to IndexedDB before network synchronization, so organizing, editing, and searching work without a connection.
Private. Notes and folder metadata are encrypted in the browser with XChaCha20-Poly1305. Passwords, private keys, folder names, and plaintext notes never reach the server. There is no password recovery.
Organized. Nested folders, a compact file tree, drag-and-drop, and accessible move controls keep larger notebooks easy to navigate.
Simple. Any notebook name and non-empty password opens an existing notebook or creates a new one. A saved browser login reopens automatically until you log out.
Publish. A note can be shared as an explicit read-only snapshot. Later private edits are not published automatically.
The app ships as one CGO-free Go binary with an embedded React frontend. It uses PostgreSQL when DATABASE_URL is set and SQLite otherwise.
Requires Go 1.27.1, Node.js 24, and npm.
git clone https://github.com/schollz/offlinenotepad
cd offlinenotepad
make build
./offlinenotepadmake build installs the locked frontend dependencies when needed, generates the ignored frontend output in internal/site/build, and embeds it in the Go binary. Docker performs the same frontend build in its Node stage, so generated assets are not kept in Git or required in the Docker build context.
Open http://localhost:8251. Without DATABASE_URL, data is stored in offlinenotepad.sqlite3 by default. See .env.example for configuration.
For a public deployment, set SITE_URL to the site's HTTPS origin (for example, https://notes.example.com). Canonical links, social previews, robots.txt, JSON-LD, the XML sitemap, and the blog's Atom feed use this value.
docker build -t offlinenotepad .
docker run -p 8251:8251 -v offlinenotepad-data:/data offlinenotepadTo stage every account from an old bbolt data.db into PostgreSQL:
./offlinenotepad -migrate /path/to/data.db --dry-run
./offlinenotepad -migrate /path/to/data.dbThe archive-wide migration does not require usernames or passwords. Users complete migration by opening their notebook with its original credentials. Back up both databases first.
The command reports aggregate progress while it reads and validates the archive and stages its records; it never prints notebook or document identifiers, ciphertext, or note content.
Isolated malformed records do not stop the archive: recoverable missing or stale legacy sync hashes are validated from the encrypted document after the user signs in. Individually unreadable documents are left in legacy staging while valid documents are promoted. Authenticated legacy records whose title is exactly deleted (ignoring surrounding whitespace and letter case) are treated as deletion markers and omitted. The UI and server logs report unreadable records and omitted deletion markers as separate aggregate counts. Progress and completion logs include aggregate diagnostic counters for every recovered or skipped category.
make serve
make test
make lint
make test-racemake serve uses Air to rebuild the embedded frontend and Go server, then reloads the browser at http://localhost:8251 when their source files change. make dev is an alias for the same development server.
Set AIR_PROXY_PORT and AIR_APP_PORT to use another pair of ports when running more than one development server.
MIT