Open Publish
unlistedby Navid Kashani
Publish a subset of your vault to your own object storage and your own static site host. Free, self-hosted, no SaaS in the middle.
Open Publish
Publish part of your Obsidian vault as a website. You own the storage and you pick the host. No subscription, no service in the middle.
Obsidian plugin → your object storage → deploy hook → your static host
Your notes go from Obsidian into your own bucket. Your host builds a site from that bucket. Nothing passes through anyone else's server, and your notes never go into Git.
What it does
- Publishes part of your vault. Pick folders, put
publish: truein a note's frontmatter, or publish one note from its right-click menu. Frontmatter wins. - Never writes to your notes. The plugin only reads them.
- Never breaks the live site. The site switches to the new version all at once, or not at all.
- Keeps
[[wikilinks]]working. The plugin ships Obsidian's own link resolution beside your notes. A link to a note you did not publish renders as plain text, never as a link to a page that is not there. - Brings your attachments along. An image embedded by a published note gets published too, wherever it lives in your vault.
- Keeps old addresses working. Renaming a note leaves a redirect behind, and one setting redirects every URL your old Obsidian Publish site used.
- Imports your Obsidian Publish folders from
.obsidian/publish.json, after showing you exactly what it would publish. - Gives you two starters. jotter is an Astro site theme and the recommended one. The Quartz starter lives here.
- Carries your site options. Navigation order, search, graph, backlinks, outline, tags, theme toggle, page metadata, previous/next links, hover previews, inline title, homepage and analytics. Hiding a page from the sidebar does not unpublish it.
Install the plugin
Three files into a folder. No terminal.
- Download
main.js,manifest.jsonandstyles.cssfrom the latest release. - Put all three in
<vault>/.obsidian/plugins/open-publish/, creating it if it is not there. - Reload Obsidian, then turn on Open Publish under Settings → Community plugins.
Or install it with BRAT, which
does the same thing and then keeps it up to date: add
navidkashani/open-publish as a beta plugin.
Open Publish is not in Obsidian's community directory yet, so there is no one-click install. The two routes above are the whole story until it is listed.
Get started
Follow docs/setup-cloudflare.md. It takes about ten minutes and needs no terminal.
Cloudflare is the default, not a requirement. Storage can be R2, Amazon S3, Backblaze B2, Wasabi, MinIO or any other S3 endpoint. Hosting can be Cloudflare Pages, Cloudflare Workers, Netlify, Vercel or any host that builds a Git repository and gives you a deploy hook URL. Cloudflare R2 without keys is the one storage entry that is not S3: it uses a small Worker in your own account, so the plugin holds a bearer token rather than a storage key.
Guides
| Guide | What it covers |
|---|---|
| Setup: Cloudflare R2 + Pages | The default path, start to finish. |
| Other providers | Every storage provider and host, in tables. |
| Troubleshooting | Every error message, and what to do about it. |
| Security | Where your key lives, and what a leak reaches. |
| The gateway | Reaching R2 without a storage key. |
| Architecture | How it works inside, and why. |
Repository layout
| Path | What it is |
|---|---|
plugin/ | The Obsidian plugin. TypeScript, no runtime dependencies. |
starters/quartz/ | The reference site template. Fetches a snapshot and builds it with Quartz. |
gateway/ | An optional Cloudflare Worker, so the plugin can reach R2 without a storage key. |
docs/ | The guides above. |
manifest.json | The plugin manifest, and the copy to edit. The build copies it next to main.js. It lives at the repository root because that is where the Obsidian community directory reads it from. |
versions.json | Which app version each plugin release needs, so an older Obsidian installs the newest release it can still run. Read from the root, beside the manifest. |
How publishing works
Each file is stored under a name made from its own contents.
objects/<ab>/<sha256> written once, never overwritten
snapshots/<id>.json one version of the site: path → hash, slug, links
current.json the only file that ever changes
A publish uploads whatever is missing, writes a snapshot, then replaces
current.json. Everything before that last write only adds. So you can quit
mid-publish and leave the live site untouched, a retry re-uploads nothing, and
rolling back is one small write. See
docs/architecture.md.
Network access
Obsidian's developer policy requires plugins to list every network endpoint. This plugin contacts three, and you configure all of them yourself:
| Endpoint | Why | When |
|---|---|---|
| Your storage endpoint, or your own Worker's address if you use the gateway | Read the current snapshot, upload notes and attachments | Scanning, publishing, cleanup |
| Your deploy hook URL | Ask your host to rebuild the site | After a successful publish |
Your site URL, path /_publish.json | Check whether the new version is live | After triggering a build |
There is no telemetry, no analytics, and no server run by this project.
Credentials
Your secret key lives in Obsidian's keychain rather than in your vault, so it does not sync with your notes and never reaches Git. Every other plugin you install can still read it, because that keychain is one shared store. So limit what a leaked key reaches: scope the token to one bucket, give your site build a separate read-only token, and revoke either in one click. docs/security.md is the full version.
Development
The test suites import the TypeScript sources directly and let Node strip the types, so there is no build step before a test run. You need Node 24.20.0 or newer.
git clone https://github.com/navidkashani/open-publish.git
cd open-publish
npm install --prefix plugin
npm run check # typecheck, lint, all three test suites, then the bundle
npm run check is what CI runs. npm run lint on its own is
eslint-plugin-obsidianmd, the same
linter the community directory's review bot runs, so a clean run here is most of
that review answered before it starts.
To try your working copy in a vault, build it and copy the three files across:
npm run build
mkdir -p "<vault>/.obsidian/plugins/open-publish"
cp plugin/main.js plugin/manifest.json plugin/styles.css \
"<vault>/.obsidian/plugins/open-publish/"
Status
0.1.0 is the first release. Phases 1 and 2 of the roadmap in
docs/architecture.md are done. Phase 3 is underway: the
Worker gateway and Site history have landed, and the Deploy-to-Cloudflare button
has not.
Mobile is the caveat, and it is why this is 0.1.0 rather than 1.0.0. The
plugin imports no Node or Electron API, does all its networking through
requestUrl, and handles the two places a phone would notice, but nobody has
run it on a device yet. If it turns out not to work there, the fix is a release
that says so in isDesktopOnly.
Licence
MIT. See LICENSE. The Quartz starter template carries Quartz's own
LICENSE.txt, also MIT, which stays as it is.
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.