Skip to content

xnohat/webobsidian

WebObsidian logo

WebObsidian

A self-hosted, Obsidian-compatible web app for your Markdown "second brain".

Point it at a folder of Markdown files and edit your notes from any browser β€” with a CodeMirror editor, live preview, wikilinks, an interactive graph, full-text search, GitHub sync (incl. Git LFS), an API for AI agents, and community-plugin support.

License: MIT Node TypeScript Docker

Quick start Β· Features Β· Configuration Β· Agent API Β· Development Β· Architecture

πŸ“ Design: PRD.md Β· πŸ“‹ Progress: IMPLEMENTATION_PLAN.md


What is this?

WebObsidian is a web application that gives you an Obsidian-like experience over a real folder of Markdown files living on your server. Your vault is 100% compatible with an existing Obsidian vault (including the .obsidian/ folder) β€” you can edit the same files from the Obsidian desktop app and from the web, side by side.

It is single-user and self-hosted: one master password protects the whole app, all configuration lives in a plain data/settings.json (no database engine), and the entire stack runs from a single docker compose up.

Why? To access and edit your knowledge base from any browser, on any device, while keeping full ownership of your files β€” and to let AI agents read/write your vault through a safe, scoped REST API.


✨ Features

  • πŸ“ Editor & rendering β€” CodeMirror 6 with live / source / reading views; wikilinks [[note]], embeds ![[file]], tags #tag, callouts, task lists, KaTeX math and Mermaid diagrams.
  • πŸ•ΈοΈ Graph view β€” force-directed graph built from your wikilinks, with fly-to node search and highlighting.
  • πŸ”— Backlinks & outline β€” right sidebar tab strip: Backlinks (linked and unlinked mentions), Outgoing links (resolved/unresolved), Tags and Outline.
  • πŸ” QMD search β€” fast full-text + fielded search (tag:, path:, title:), fuzzy + prefix matching, incremental indexing, persisted to disk for fast startup.
  • πŸ”„ GitHub sync β€” native git pull / commit / push with Git LFS for large attachments, optional auto-sync, and per-file version history (browse & restore).
  • πŸ” Login gate β€” a single master password (scrypt-hashed) protects everything; JWT in an httpOnly cookie.
  • 🌐 Public sharing β€” turn any note into a read-only, server-rendered (SEO-friendly) public page at /share/<token>, optionally password-protected.
  • πŸ€– Agent API β€” scoped API keys (read / write / search) let AI agents work with the vault over REST at /api/v1. See docs/AGENT_API.md.
  • 🧩 Community plugins β€” install Obsidian plugins from GitHub; loaded against an Obsidian-API compatibility shim (subset support).
  • πŸ“± Responsive / mobile β€” drawer sidebars, edge-swipe, an on-keyboard formatting toolbar, and touch-friendly targets, Γ  la Obsidian Mobile.
  • πŸ—ƒοΈ Pure-JSON config β€” everything lives in data/settings.json. No database.
  • 🐳 Docker β€” one command to run the whole stack.

πŸš€ Quick start (Docker)

git clone https://github.com/xnohat/webobsidian.git
cd webobsidian
cp .env.example .env          # edit VAULT_HOST_PATH, set WEBOBSIDIAN_PASSWORD
docker compose up -d --build
# open http://localhost:8787

Out of the box it serves the bundled ./sample-vault, so the stack boots immediately. All deployment settings live in .env (git-ignored) β€” you never edit the tracked docker-compose.yml, so a git pull / redeploy keeps your config and vault mapping intact.

πŸ–₯️ Desktop app (no server setup)

Prefer a native app? Grab an installer from the Releases page β€” available for macOS / Windows / Linux (arm64 Β· x64 Β· ia32):

Platform Download
macOS .dmg (or .zip) β€” arm64 / x64
Windows NSIS installer .exe or portable .exe β€” x64 / arm64 / ia32
Linux .AppImage or .deb β€” x64 / arm64

The desktop app bundles the whole server, picks your vault folder on first launch, and logs you in automatically β€” no password to type, no Docker. Apps are currently unsigned, so macOS Gatekeeper / Windows SmartScreen will warn on first open (right-click β†’ Open on macOS). Build it yourself with npm run desktop:dist; see desktop/README.md for details.

πŸ”‘ Default password is 123456. Log in right away, then change it in Settings β†’ Account. To seed a different password on first run, set WEBOBSIDIAN_PASSWORD in .env. Forgot it? Set WEBOBSIDIAN_PASSWORD (plaintext) or auth.passwordHash (scrypt) as a recovery override.

Point it at your own vault

# .env
VAULT_HOST_PATH=/abs/path/to/your/ObsidianVault   # must exist; bind-mounted to /vault
WEBOBSIDIAN_PASSWORD=use-a-strong-password
HTTP_BIND=0.0.0.0                                  # 127.0.0.1 to expose only to localhost
HTTP_PORT=8787

Then docker compose up -d --build. Your vault can be a plain folder or a git clone (Git LFS is supported for attachments).

Behind a reverse proxy (TLS)

Set HTTP_BIND=127.0.0.1 so the app is only reachable from the host, then terminate TLS with nginx / Caddy / Traefik in front of http://127.0.0.1:8787.

Large vaults & file watching

A fresh VPS ships a low fs.inotify.max_user_watches (often 8192), which a big vault exceeds. WebObsidian auto-detects this and falls back to polling (works anywhere, higher CPU). For lower CPU, raise the kernel limit and keep native watching:

sudo sysctl -w fs.inotify.max_user_watches=524288
echo 'fs.inotify.max_user_watches=524288' | sudo tee -a /etc/sysctl.conf

The search index (QMD) and link graph are kept in memory, so memory use scales with the number of notes. The Docker image sets NODE_OPTIONS=--max-old-space-size=4096 (4 GB); raise it to 8192 for very large vaults (e.g. 6k+ notes / multi-GB).


πŸ’» Local development

Requires Node β‰₯ 20 and git (+ git-lfs if you use LFS).

npm install
npm run dev          # server on :8787 + web dev server on :5173 (proxied)
# open http://localhost:5173

Production build (the server serves the built SPA):

npm run build
VAULT_PATH=./sample-vault npm start
# open http://localhost:8787

Useful scripts:

Command What it does
npm run dev Run server + web together in watch mode
npm run build Build the web SPA, then compile the server
npm start Run the production server (serves built web)
npm run typecheck Type-check both workspaces

βš™οΈ Configuration

Docker env (.env, consumed by docker-compose.yml)

Var Default Description
VAULT_HOST_PATH ./sample-vault Host path bind-mounted to /vault
HTTP_BIND 0.0.0.0 Host interface to publish on (127.0.0.1 = local only)
HTTP_PORT 8787 Host port mapped to container 8787
WEBOBSIDIAN_PASSWORD – Seed/override the master password
WEBOBSIDIAN_WATCH auto auto (native + polling fallback) or polling

App-level env (read by the server; Docker sets these inside the container)

Var Default Description
PORT 8787 HTTP port
VAULT_PATH ./sample-vault Path to the notes vault
DATA_DIR ./data Where settings.json + search index live
ALLOWED_ROOTS – Comma-separated roots the vault picker may browse
WEBOBSIDIAN_PASSWORD – Seed/override the master password
WEBOBSIDIAN_WATCH auto File-watch mode: auto or polling
NODE_OPTIONS --max-old-space-size=4096 Node heap size β€” raise for large vaults

Everything else β€” git remote/token, API keys, plugins, theme β€” is configured in the Settings UI and stored in data/settings.json.


πŸ€– Agent API

Scoped REST API for AI agents at /api/v1. Create an API key in Settings β†’ API Keys, then pass it as a header. Full reference: docs/AGENT_API.md.

🧩 Drop-in agent skill (no clone needed)

Want your coding agent (Claude Code, Codex, OpenCode, Cursor…) to work with your vault directly? Copy the block below and paste it to your agent. It downloads the skill from GitHub, asks for your URL + API key once, saves them to ~/.webobsidian/credentials.json, and can then read/write/search your notes on request.

First create an API key in the app at Settings β†’ API Keys (scopes read/write/search).

SYSTEM / TASK β€” Install the "webobsidian" agent skill from GitHub, then set up credentials.
Do every step in order; do not skip the credential step.

STEP 1 β€” Download the skill into your runtime's skills directory
  (Claude Code: ~/.claude/skills Β· Codex: ~/.codex/skills Β· OpenCode: ~/.opencode/skills):
      SKILLS_DIR=~/.claude/skills        # change for your runtime
      mkdir -p "$SKILLS_DIR/webobsidian"
      curl -fsSL https://raw.githubusercontent.com/xnohat/webobsidian/main/docs/agent-skill/webobsidian/SKILL.md \
        -o "$SKILLS_DIR/webobsidian/SKILL.md"

STEP 2 β€” Set up credentials (ASK ME; never echo the key back). Ask me for my WebObsidian
  base URL (e.g. https://notes.example.com) and my API key (looks like wok_...), then:
      mkdir -p ~/.webobsidian && chmod 700 ~/.webobsidian
      printf '{ "baseUrl": "%s", "apiKey": "%s" }\n' "<BASE_URL>" "<API_KEY>" > ~/.webobsidian/credentials.json
      chmod 600 ~/.webobsidian/credentials.json

STEP 3 β€” Verify (do NOT print the key) and confirm ready:
      BASE=$(python3 -c 'import json,os;print(json.load(open(os.path.expanduser("~/.webobsidian/credentials.json")))["baseUrl"].rstrip("/"))')
      KEY=$(python3 -c 'import json,os;print(json.load(open(os.path.expanduser("~/.webobsidian/credentials.json")))["apiKey"])')
      curl -s "$BASE/api/v1/health"
      curl -s -H "X-API-Key: $KEY" "$BASE/api/v1/tags" | head
  From now on, when I ask you to work with my WebObsidian / Obsidian vault, use the webobsidian skill.

Details & alternatives: docs/agent-skill/INSTALL.md Β· canonical skill: docs/agent-skill/webobsidian/SKILL.md.

KEY=wok_your_key_here
BASE=http://localhost:8787/api/v1

# list notes
curl -H "X-API-Key: $KEY" "$BASE/notes?limit=10"

# create / update a note
curl -X PUT -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"content":"# From the agent\n\nHello vault."}' \
  "$BASE/notes/Agent/Generated.md"

# search (fielded queries supported: tag:, path:, title:)
curl -H "X-API-Key: $KEY" "$BASE/search?q=tag:idea%20graph&limit=5"
Endpoint Scope Description
GET /api/v1/notes read List notes (paginated)
GET /api/v1/notes/{path} read Read a note + metadata
PUT /api/v1/notes/{path} write Create / overwrite
PATCH /api/v1/notes/{path} write Append content
DELETE /api/v1/notes/{path} write Move to trash
GET /api/v1/search?q= search QMD search
GET /api/v1/backlinks?path= read Backlinks for a note
GET /api/v1/tags read All tags with counts

πŸ—οΈ Architecture

Monorepo with two npm workspaces:

webobsidian/
β”œβ”€β”€ server/   # Express + TypeScript API
β”‚   └── src/{routes,services,middleware,plugins}
β”œβ”€β”€ web/      # React + Vite SPA (built into server/public)
β”‚   └── src/{components,lib,styles}
β”œβ”€β”€ data/     # runtime: settings.json + search index (git-ignored)
β”œβ”€β”€ docs/     # AGENT_API.md, Obsidian internals notes
β”œβ”€β”€ Dockerfile Β· docker-compose.yml Β· .env.example
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Browser (React SPA) ────────────────────────┐
β”‚   CodeMirror 6 Β· Live Preview Β· File Tree Β· Graph Β· Search           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚ REST + WebSocket                  β”‚ static assets
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Server (Node + Express + TypeScript)                β”‚
β”‚   Auth gate β”‚ Vault FS β”‚ QMD Search β”‚ Git Sync β”‚ API Gate β”‚ Plugins  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
   settings.json   Vault dir   Search index  GitHub repo    plugins dir
   (JSON config)   (.md+attach) (in-mem/disk) (git + LFS)   (.obsidian/plugins)

Tech stack: Node 20+ Β· Express Β· TypeScript Β· React Β· Vite Β· CodeMirror 6 Β· unified/remark/rehype Β· MiniSearch (QMD) Β· simple-git + git-lfs Β· scrypt + JWT Β· Docker.

See PRD.md Β§2 for the full design.


πŸ”’ Security notes

  • Master password is scrypt-hashed; the JWT secret is auto-generated.
  • API keys are hashed at rest and scoped (read / write / search) with per-key rate limiting and audit logging.
  • All file paths are guarded against traversal; the vault picker is confined to ALLOWED_ROOTS.
  • Secrets (git token / API keys) live in data/settings.json on the server β€” mount /data as a private volume and keep it off version control. Change the default password.

πŸ—ΊοΈ Compatibility & scope

  • βœ… Works directly on an existing Obsidian vault, including .obsidian/ config.
  • ⚠️ Single-user (v1) β€” no real-time multi-user collaborative editing yet.
  • ⚠️ Git sync replaces Obsidian Sync/Publish.
  • ⚠️ Community-plugin support is a subset of the Obsidian API; plugins relying on Electron/Node internals may not work.

🀝 Contributing

Contributions are welcome! A few house rules from CLAUDE.md:

  1. Follow PRD.md. It is the source of truth for design. Changing scope means updating the PRD first (with a changelog bump), then the code.
  2. Keep IMPLEMENTATION_PLAN.md in sync β€” flip checkboxes and add a progress-log line as you work.
  3. TypeScript everywhere; avoid any. Runtime config is JSON only (no DB engine).
  4. Never log secrets/tokens; hash before storing; guard against path traversal.

Run npm run typecheck before opening a PR.


πŸ“„ License

MIT Β© xnohat


Built for people who want to own their notes. Not affiliated with Obsidian.md.

About

Web based Obsidian Notes App

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages