Content sources
Publish Obsidian notes as a docs site
Publish a vault of Obsidian notes as a docs site, with wikilinks, heading links, and images resolved at build time.
By Hayden Bleasel7 min read

To publish Obsidian notes as a docs site, point Blume's obsidian() source at the vault folder. The notes become a section of the site, with a sidebar, search, and a stable URL for every note. Blume reads the vault in place, so there's no export step: you keep writing in Obsidian, and wikilinks, heading links, and images resolve when the site builds.
When a vault fits
This works best for notes that are already written to be read by other people: an engineering handbook, runbooks, onboarding notes, or the design docs for an open-source project. Blume turns Obsidian's link syntax into site links and publishes everything else as Markdown, beside any regular docs pages you have.
It doesn't recreate Obsidian on the web. There's no graph view or backlinks panel, and Obsidian's callouts and embeds aren't converted. If what you want is a digital garden that looks and behaves like your vault, Quartz is a static site generator built for that, with backlinks and a graph view, and Obsidian Publish is Obsidian's own hosted publishing service.
Create the project
Scaffold a Blume project:
npx blume init handbook-sitePick the docs template. When it asks where your content lives, select both filesystem and obsidian. That writes a config with both sources, a docs/ folder for regular pages, and a vault/ folder with a first note in it. Open vault/ in Obsidian with Open folder as vault.
Now point the Obsidian source at your section. Here the notes publish under /handbook, and a folder of note templates stays out:
import { defineConfig } from "blume";
import { filesystem, obsidian } from "blume/sources";
export default defineConfig({
title: "Acme Handbook",
description: "How the Acme platform team builds and runs its services.",
content: {
sources: [
filesystem({ root: "docs" }),
obsidian({
vault: "vault",
prefix: "handbook",
exclude: ["Templates"],
}),
],
},
});vaultis the vault's directory, relative to the project root. Keep it inside the project, and inside the repository you deploy from, so your host can build it.prefixputs every note under/handbookand gives the section its own group in the sidebar. Leave it out to publish the notes at the site root.excludetakes folder names, not globs, and skips them at any depth. Dot-folders are always skipped, including Obsidian's own.obsidiansettings and.trash.
Build a vault that's safe to publish
Blume publishes every note in the vault except the folders it skips. It doesn't read Obsidian Publish's publish property: that property is dropped when a note loads, so publish: false hides nothing. Don't point a build at your personal vault. Give the site a vault of its own that holds only notes you'd put on the web.
This guide uses a small engineering handbook:
vault/
├── .obsidian/
├── index.md
├── On call.md
├── Onboarding/
│ ├── Setup.md
│ └── First week.md
├── Services/
│ ├── Deploys.md
│ └── deploy-flow.png
└── Templates/
└── Runbook.mdDelete the Welcome.md note that init seeded, then add these. The vault's index.md is the section's landing page:
---
title: Acme handbook
description: How the platform team builds, ships, and runs its services.
---
New to the team? Start with [[Setup]], then read [[First week]].
When something breaks, [[On call]] has the rotation and who to page.Deploys.md has a Markdown image, a heading other notes link to, and a link to a note nobody has written yet:
---
description: How a change gets from a merged pull request to production.
---
Every merge to main deploys to staging, then to production once checks pass.

## Rolling back
Revert the merge commit on main. The revert deploys like any other change.
Afterward, write it up using [[Incident review]].By Friday, you should have shipped one change to production.
1. Pick a starter issue from the team board.
2. Ship it through the flow in [[Deploys]].
3. Practice [[Deploys#Rolling back|rolling it back]] on staging.Fill Setup.md and On call.md with whatever you like. Each note publishes at a route built from its path, lowercased with spaces turned into hyphens:
| Note | Route |
|---|---|
index.md | /handbook |
On call.md | /handbook/on-call |
Onboarding/First week.md | /handbook/onboarding/first-week |
Services/Deploys.md | /handbook/services/deploys |
Templates/Runbook.md | Not published |
A note with no title property is titled by its filename, the same rule Obsidian uses, so First week.md is "First week" in the sidebar.
Check links and images
Start the dev server:
npx blume devOpen http://localhost:4321/handbook and follow the links. Wikilinks resolve by note name across the whole vault, the way Obsidian resolves them, so [[Setup]] finds Onboarding/Setup.md without a path. Here's how the rest of Obsidian's syntax comes through:
| In the vault | On the site |
|---|---|
[[Note]], [[Note|text]] | A link to the note's page |
[[Note#Heading]], [[#Heading]] | A link to that heading |
[[folder/Note]] | A link by path, for names more than one note shares |
 | The image, served from the vault |
%%comment%% on one line | Removed |
Properties Blume knows, like title and draft | Kept |
Other properties, like tags, aliases, and publish | Dropped |
![[image.png]] embeds | Not converted |
> [!note] callouts | A plain blockquote |
%%comments%% across several lines | Left in place |
Images need the most care. Obsidian inserts a pasted image as an ![[...]] embed, which Blume doesn't convert, so rewrite each one as a Markdown image. Obsidian renders those too. Blume resolves the path relative to the note, so keep the image beside the note, as deploy-flow.png is here, or write the path from the note to the file.
Vault notes are Markdown, not MDX, so Blume's components and ::: callouts aren't available in them. Pages in docs/ can use both, and can link into the handbook by route, like /handbook/services/deploys.
Order the sidebar
The vault's folders become sidebar groups under Handbook, named after the folders. Within a folder, the index note comes first, then notes sort by name, so Setup would land after First week. To set the order yourself, give each note a sidebar.order:
---
sidebar:
order: 1
---Give First week.md an order of 2. Obsidian's Properties panel doesn't support nested properties like sidebar, so add them in source mode. The same block takes a label for a shorter sidebar name, and hidden: true to keep a note published but out of the sidebar.
You can order by name instead. A note named 01 Setup.md sorts first and still publishes at /handbook/onboarding/setup, and the same prefix orders folders. Its title keeps the number, though, so give numbered notes a title property.
Blume doesn't read meta.ts files inside a vault, so folder labels come from folder names and order comes from notes' properties and names.
Fix the warnings
The dev server's terminal shows a warning for the link to [[Incident review]]:
BLUME_WIKILINK_UNRESOLVEDmeans a link points at a note that doesn't exist, so it renders as plain text. The same code covers a link to a heading that doesn't exist, which keeps the page link and drops the anchor.BLUME_WIKILINK_AMBIGUOUSmeans more than one note has the name a link uses. The link goes to the first one in vault order; write a longer path, like[[Services/Deploys]], to pick one.
Neither fails the build, so a vault in the middle of a rewrite still publishes. Fix this one by writing the note, or by removing the link. To make CI fail on these, and on any other warning, run:
npx blume validate --strictThen build the site. It writes static files to dist/:
npx blume buildKeep drafts and private notes out
Blume's own draft property works on vault notes. A note with draft: true shows in blume dev, so you can preview it, and blume build leaves it out. For whole folders, add their names to exclude, as with Templates.
Check the result before you deploy. Serve the build and click through the Handbook section, and search for a phrase that only appears in a draft or an excluded note. Nothing should come back.
npx blume previewDeploy
The build is a folder of static files, so it deploys anywhere. Deploy Markdown docs to GitHub Pages walks through one host from start to finish, including a workflow that rebuilds on every push. On a host Blume doesn't detect a site URL for, set deployment.site so the sitemap and social cards get absolute URLs. See Deployment for the rest.
Because the vault lives in your repository, setting lastModified to "git" gives its pages "Last updated" dates from their commit history, like any other page.
Edit a note and rebuild
With blume dev running, edit a note in Obsidian and the page updates. Blume ignores changes inside .obsidian and .trash, so rearranging panes doesn't trigger a rebuild.
The live site updates when it rebuilds. Commit the vault and push, and a host that builds on every push publishes the change. Blume resolves wikilinks by note name, so moving a note to another folder doesn't break the links to it, but it does change the note's URL. Add a redirect from the old route when other sites link to it.
Troubleshooting
A note is missing from the site
Check that it isn't in a dot-folder or a folder named in exclude, and that it doesn't have draft: true. A note whose filename contains # or ? can't be loaded: the dev server leaves it out, and blume build stops, both with a BLUME_UNLOADABLE_FILE_NAME error. Rename it.
An image doesn't show
It's probably an ![[...]] embed, which stays as written. Rewrite it as , with the path relative to the note.
A link shows as plain text
The target note doesn't exist under that name. Links to an alias count as missing too: Blume drops aliases rather than resolving them, so link to the note's real name.
A link goes to the wrong note
Two notes share the name. Link with a longer path, like [[Onboarding/Setup]], or rename one of them.
A callout shows as a quote
Obsidian callouts render as plain blockquotes. Write the note so it still reads well as a quote, or move that page into docs/ as MDX and use a :::note callout.
Next step
Publish your vault
Scaffold a project with the Obsidian source, then move the notes you want public into its vault folder.
npx blume init handbook-siteA step here not working for you? Report a broken step.