Kladde is an agent-friendly notes app that syncs to a folder with plain markdown files. Kladde is Danish for draft and sounds kind of like Clawd if you squint hard enough.
- Offline-first — IndexedDB cache with background sync and retry with exponential backoff
- WYSIWYG + Markdown — Tiptap editor with toggle to plain markdown mode
- Task lists — Clickable checkboxes
- Star notes — Starred notes float to top with smooth FLIP animation
- Search — Filter by title and content with highlighted snippets
- Auto-save — No save button, status shown as subtle icon (synced/syncing/offline/error)
- Mobile-optimized — Full-screen list ↔ editor with slide transitions, no hover on touch
- Editable titles — Rename notes inline, server auto-deduplicates with
(n)suffix - Delete notes — Via ⋮ menu with confirmation
- Dark mode — Respects system preference
- PWA — Installable, works offline, service worker with precaching
- Frontend: Vue 3, TypeScript (strict), Tiptap, Pinia, Vue Router, Vite+ (Vite, Vitest, Oxlint, Oxfmt, Rolldown), Workbox
- Backend: Go — serves API + built PWA, stores notes as plain
.mdfiles - Icons: Lucide
client/ Vue + Vite+ PWA
server/ Go server
- Node.js + npm (for
client/) - Go 1.22+ (for
server/)
From repo root:
# Install frontend deps + run frontend static checks
cd client
npm ci
npx vp check
npx vp build
# Build backend
cd ../server
mkdir -p dist
go build -o ./dist/kladde-server .From client/:
# Unit + static validation
npm run check
# Browser install (once per machine)
npm run test:e2e:install
# End-to-end tests
npm run test:e2eThe Playwright suite runs separately from vp check because it needs a browser plus the Go backend; vp check stays the fast static/unit gate.
This produces:
- Frontend static assets in
client/dist - Server binary in
server/dist/kladde-server
The server reads runtime configuration from a JSON options file.
For normal startup, --options <directory> is the only runtime CLI argument (it loads <directory>/options.json).
Example production file (/etc/kladde/options.json):
{
"addr": ":8080",
"notes": "/var/data/kladde/notes",
"client": "/var/www/kladde/client/dist",
"gitBackup": {
"enabled": true,
"remote": "https://github.com/your-org/kladde-backup.git",
"githubToken": "ghp_your_personal_access_token",
"pushIntervalSeconds": 300,
"authorName": "kladde backup",
"authorEmail": "kladde@localhost"
}
}Fields:
addr— HTTP listen addressnotes— path to notes directoryclient— path to built frontend assets (client/dist)gitBackup.enabled— enable automatic git commit + push backupsgitBackup.remote— git remote URL (required whengitBackup.enabled=true)gitBackup.githubToken(optional) — GitHub personal access token for authenticated push (see Creating a GitHub token)gitBackup.pushIntervalSeconds(optional, default300) — minimum seconds between push attemptsgitBackup.authorName/gitBackup.authorEmail— commit author identity
Derived paths (not configurable in options.json):
<options-dir>/users.json<options-dir>/shares.json
Backup behavior:
- Backup runs automatically on note changes
- Pushes target the remote's default branch (
origin/HEAD) - If
gitBackup.githubTokenis set, HTTPS GitHub auth uses that token - If
gitBackup.githubTokenis not set, git falls back to normal host auth (SSH keys, credential helper, etc.) - Pushes are rate-limited by
gitBackup.pushIntervalSeconds(default 5 minutes)
To use gitBackup with a GitHub remote, you need a fine-grained personal access token:
- Go to github.com/settings/personal-access-tokens/new
- Name: e.g.
kladde-backup - Expiration: pick what suits you (up to "no expiration")
- Repository access: "Only select repositories" → pick your backup repo
- Permissions → Repository permissions: Contents → Read and write
- Click Generate token and copy it into
gitBackup.githubTokenin your options file
No other permissions or scopes are needed.
From repo root:
mkdir -p tmp/notes
cat > tmp/options.json <<'JSON'
{
"addr": "127.0.0.1:8080",
"notes": "./tmp/notes",
"client": "./client/dist"
}
JSON
./server/dist/kladde-server --options ./tmpHealth check:
curl http://127.0.0.1:8080/api/health/client-api/*— web app API (cookie/session auth)/api/*— agent API (Bearer token auth, markdown-first)
Agent API is self-documenting:
curl http://127.0.0.1:8080/apiBefore logging in, create a user (users.json lives in the options directory):
./server/dist/kladde-server adduser \
--users ./tmp/users.json \
--username admin \
--password "<choose-a-strong-password>" \
--name "Admin"API keys are stored in apikeys.json in the options directory.
The command prints the token once (copy it immediately):
./server/dist/kladde-server addapikey \
--keys ./tmp/apikeys.json \
--username admin \
--name "my-agent" \
--readonlyExample usage:
TOKEN="kld_..."
# List notes
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/notes
# Create/update a note inside collection "opskrifter"
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/markdown" \
--data-binary $'# Carbonara\n\n- Æg' \
http://127.0.0.1:8080/api/notes/opskrifter/carbonara
# Rename/move a note
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"from":"opskrifter/carbonara","to":"aftensmad/carbonara"}' \
http://127.0.0.1:8080/api/move# Build client
cd client && npm run build
# Build + deploy server
cd ../server && go build -o server .
sudo systemctl stop kladde
sudo cp server /usr/local/bin/kladde
sudo systemctl start kladdeOptionally served behind a reverse proxy (nginx, Caddy, etc.) for TLS and domain routing.