ZenNotes: a keyboard-first Markdown vault that keeps your files on disk
Keyboard-first local Markdown notes with Vim motions, diagrams, and MCP integration.
At a glance
- What is it?
- ZenNotes is an MIT-licensed Electron notes app with Vim motions, a Go-based self-hosted server, and a first-party MCP server. It stores plain .md files in a vault folder, and the README is explicit about what that means for portability.
- Who is it for?
- Adopt ZenNotes if you want Vim-style navigation over a folder of ordinary .md files and you are willing to run a desktop app that auto-updates, or to self-host the Go server with docker-compose.yml. Skip it if you need a mobile client today, since the README lists desktop, self-hosted and a planned hosted mode but no phone app.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ZenNotes solves, and for whom
Most note apps treat your writing as rows in a database they own. ZenNotes inverts that. The README states that every note is a normal .md file inside a chosen vault and that ZenNotes does not store note content in a hidden database. The app is a layer on top of a folder: Vim motions, split and preview panes, tasks, tags, archive and trash, diagrams, search, daily notes, and CSV-backed table and board views.
The audience is narrower than "anyone who takes notes". It is people who already keep a folder of Markdown and want keyboard speed on top of it, and people who want a tool that can read that same vault. The README frames the MCP server this way: tools work on "the same vault you do instead of a copy". If you have never used Vim motions, the feature list reads as a wall of keybindings you will not press. If you have, the pitch is that you keep your files and gain an editor.
One detail worth noticing before anything else: the README carries a note that the author builds ZenNotes with heavy help from AI tools such as Claude and Codex, and that the architecture and trade-offs are still his own decisions. That is unusually candid for a project README, and it sets expectations about how the code was produced.
One core, three runtimes, and a Go server behind the web build
The repository is an npm workspace monorepo with a single shared product core. The README names three modes: desktop, an Electron shell with native menus, an updater and detached note windows; self-hosted, a browser frontend plus a Go server aimed at home servers and LAN use; and hosted, described only as planned, using the same web and server stack with auth and multi-user storage added later. The source of truth for user-facing features is the shared UI in packages/app-core, so the desktop and web builds are meant to diverge in their shell, not in their feature set.
The Dockerfile makes the data flow concrete. It has three stages. A web-build stage runs npm ci and builds the apps/web workspace into a static bundle. A server-build stage compiles the Go server in apps/server with that web bundle embedded. The runtime stage keeps only the server binary. The comment in the Dockerfile adds a constraint that matters if you build for another architecture: the web bundle is platform-agnostic, so the web-build stage should run on the native build platform and never under emulation.
The compose file shows how the vault is mounted. The host content root, defaulting to ./vault, is mounted at /workspace inside the container, and a data directory is mounted at /data. The server binds to 0.0.0.0:7878 internally, while the published port is bound to 127.0.0.1 on the host by default. The container runs read_only with a tmpfs at /tmp, drops all capabilities, and sets no-new-privileges. Those are deliberate choices, and they mean the container cannot write outside the mounted volumes.
Installing ZenNotes and opening a first vault
Desktop installers are attached to each GitHub Release, and the README says the app auto-updates, so you download once. On macOS the recommended path is the Homebrew cask:
brew install --cask zennotes/tap/zennotesAlternatively you download the .dmg for your chip and drag ZenNotes to Applications. The README states those builds are signed and notarized. On Windows you run ZenNotes-<version>-win-x64.exe. On Linux the README recommends the AUR package on Arch-family distros because it installs without libfuse2:
yay -S zennotes-binDebian and Ubuntu users install the .deb directly. AppImage users need FUSE 2, and the README gives an escape hatch for distros that ship only FUSE 3:
./ZenNotes-<version>-linux-x86_64.AppImage --appimage-extract-and-runFor a first real use, the desktop app installs a zen command-line companion from Settings, then CLI. The README lists what it does: list, read, search, capture, edit, archive and trash notes, plus tasks, folders, and MCP. On macOS it can also install the Raycast extension locally. Once a vault is chosen, the editor is where the keyboard-first claim is tested: the README names first-class Vim mode, leader-key flows, a command palette, pane and tab motion, local ex commands, and built-in help.
If you would rather run the server, the repository ships a Makefile and a compose file. The Makefile's help output documents make up to build and start the self-hosted server, make logs to follow logs, and make open to launch the app in your browser. The compose file publishes port 7878 and expects a vault directory and a data directory on the host.
Where the plain-file model costs you
Plain files are the selling point, and they are also the source of the sharpest limitations. The README describes system folders that still exist alongside a flexible vault model: quick, archive, and trash are built-in lifecycle areas, and the main notes area can be either inbox/ or the vault root for Obsidian-style flat vaults. Folder labels are customizable in the UI without changing internal ids. That flexibility is real, but it means the on-disk layout is a setting you can get wrong, and the README does not document a migration path if you later change your mind about which layout you want.
The CSV databases are the second place to look carefully. Table and Board views are described as sitting over plain .csv files, which keeps them portable, but a CSV has no schema, no types, and no referential integrity. Anything you would want from a relational store has to be enforced by the app, not the file.
Self-hosting has its own failure modes. The compose file defaults ZENNOTES_ALLOW_INSECURE_NOAUTH to 0 while defaulting ZENNOTES_AUTH_TOKEN to an empty string. The README does not explain what the server does when auth is required but no token is configured, so that combination is the first thing to check in your own deployment rather than assume. The Dockerfile also pins base images by digest and tells you to refresh them deliberately, which is good practice but pushes image maintenance onto you.
The README does not document rollback, and it does not describe what happens to an open vault if two clients write the same file at once. It does say ZenNotes watches the vault for external changes, which is the mechanism you would rely on, but concurrency semantics are not spelled out.
ZenNotes compared with Obsidian
The comparison people actually search for is ZenNotes versus Obsidian, and the README invites it by naming flat vaults as an Obsidian-style layout. Both keep Markdown files on disk. The difference is where the editor sits in the product. Obsidian's core is a plugin-driven editor with a large third-party ecosystem; ZenNotes ships a fixed feature set (Vim motions, split and preview panes, tasks, tags, daily and weekly notes, CSV table and board views, diagrams) and adds a first-party MCP server plus a Go server you can run yourself.
That cuts both ways. A fixed feature set means fewer surprises and no plugin compatibility matrix to debug. It also means the features you get are the ones the maintainer chose. The MCP server is the clearest divergence: ZenNotes exposes the vault to MCP-capable tools through a first-party server rather than leaving that to a community plugin, which is a stronger integration promise and a larger surface to audit.
The self-hosted mode separates them further. ZenNotes ships a Dockerfile and docker-compose.yml for a browser frontend backed by a Go server, which the README describes as suitable for home servers and LAN use. If you want a browser client on your own hardware without a sync service in the middle, that is the path, and it is not the path Obsidian takes.
Licence, maintenance and upgrade cost
ZenNotes is MIT licensed, with a LICENSE file at the repository root. MIT is permissive: you can use, modify and redistribute the code, including commercially, provided the copyright notice and permission notice are preserved. The Dockerfile and compose file also pin third-party base images, and the root package.json lists overrides for @excalidraw/excalidraw and @excalidraw/mermaid-to-excalidraw, so the dependency tree includes components under their own licences. If you redistribute a build, those notices are your responsibility. This is not legal advice; read the LICENSE file and the dependency licences yourself.
On maintenance, the repository is not archived, and the last push was on 2026-09-09. Releases are frequent: v2.47.0, v2.46.0 and v2.45.0 all landed within the week before that push, and the root package.json carries version 2.50.2. The desktop app auto-updates, which lowers the cost of staying current but also means you do not choose when a new version arrives. For self-hosted deployments the upgrade cost is different: you rebuild the image from the Dockerfile, which re-runs npm ci and the Go build, so the bottleneck is build time and the digest pins you must refresh rather than a version number you edit.
The monorepo requires Node 22 or newer according to the engines field, and it declares [email protected] as the package manager. Building from source therefore starts with the right Node major version, not with the latest one you happen to have installed.
Editorial conclusion
Adopt ZenNotes if you want Vim-style navigation over a folder of ordinary .md files and you are willing to run a desktop app that auto-updates, or to self-host the Go server with docker-compose.yml. Skip it if you need a mobile client today, since the README lists desktop, self-hosted and a planned hosted mode but no phone app. Before committing a vault, verify two things: that the MCP server exposes only the operations you want a tool to have on your notes, and that the ZENNOTES_AUTH_TOKEN value in your compose environment is non-empty, because the compose file defaults ZENNOTES_ALLOW_INSECURE_NOAUTH to 0 and a blank token plus that default is the configuration to test first.
Frequently asked questions
Does ZenNotes store my notes in a database?
No. The README states that every note is a normal .md file inside a chosen vault and that ZenNotes does not store note content in a hidden database. Tasks, tags and the CSV table views are layered on top of those files.
Can I self-host ZenNotes on my own server?
Yes. The repository ships a Dockerfile and docker-compose.yml for a browser frontend backed by a Go server, which the README describes as suitable for home servers and LAN use. The compose file publishes port 7878 and mounts a vault directory plus a data directory.
Is there a ZenNotes mobile app?
The README lists desktop, self-hosted and a planned hosted mode, and does not mention a mobile client. The desktop app installs a zen CLI companion, and on macOS it can install the Raycast extension locally, but no phone build is documented.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zennotes-zennotes)