For anyone who writes content in Obsidian and wants to publish to their own website without a separate CMS
Blueprint: Obsidian as CMS
You write content in Obsidian but publish through a separate CMS — copying text, reformatting, re-entering metadata in yet another interface. Or worse: content lives in both the vault and the site codebase, and you never know which version is current.
This Blueprint removes the middleman. Your vault becomes the single source of content for the site. You write in markdown, control behavior through YAML frontmatter (slug, type, access level), and a sync script transforms everything into web-ready formats. Edit a file, run sync, it’s live.
What You Get
- Vault as the single source of content — no copies in the codebase, no drift between versions
- Sync script — reads the vault, converts markdown to HTML/JSON, generates a navigation manifest
- Control via YAML — slug, page type, access level, sort order — all in frontmatter, no code changes needed
- Auto-generated manifest — new file in the vault = new item on the site, zero manual wiring
How to Apply
- Open a coding agent in the site project directory (not the vault)
- Feed it this Blueprint: “I need a CMS based on Obsidian, build it from this Blueprint”
- The agent will ask questions — including “where is your vault?” (absolute path)
- Answer about your stack, content types, deployment
- The agent writes the sync script, sets up the pipeline, and verifies against real vault files
Context: one session from a code project. The agent reads the vault via an absolute path and writes code into the project. Vault-side work is minimal (create folders, add YAML to files) — the agent handles this through file access.
When to Use
- A site with text content (blog, docs, guides, courses, landing pages)
- The content manager already works in Obsidian and doesn’t want to switch to a CMS
- Fast iteration is needed: edit .md, run sync, it’s live
- You want to leverage Obsidian features (wikilinks, graph view, templates) for content work
Core Idea
Obsidian is the editor. YAML frontmatter is the config. The sync script is the engine. The site is the output.
The human:
- Writes content in markdown
- Controls behavior through YAML frontmatter (slug, type, access)
- Organizes content by folder (folder = content type)
- Triggers publishing (“deploy” or “update site”)
The AI:
- Writes and maintains the sync script
- Parses YAML and transforms markdown into the target format
- Generates the content manifest
- Deploys
Architecture
Obsidian Vault (source of truth)
│
├── Folder A/ .md files with YAML frontmatter
├── Folder B/ .md files with YAML frontmatter
├── Folder C/ .md files with YAML frontmatter
│
└──── Sync script ─────┐
│ │
▼ ▼
HTML/JSON files manifest.json
(in site project) (content index)
│
▼
Deploy
The key point: the vault and the site project are separate directories. The sync script reads from the vault and writes to the project. The vault knows nothing about the site. The site knows nothing about Obsidian.
Key Principles
1. Vault is the single source of content
Content lives only in the vault. No copies in docs/, content/, or in code. The sync script always reads from the vault directly. To update text on the site — edit the .md in Obsidian, run sync.
2. YAML frontmatter controls behavior
Every .md file contains YAML frontmatter that defines how the sync script processes it:
---
slug: getting-started # URL path on the site
type: page # output type (page, fragment, data)
access: public # access level (public, authorized, cohort-code)
title: Getting Started # title (if different from the # heading in the body)
description: Quick start # description for the manifest and SEO
order: 1 # position in navigation
---
YAML is the contract between the content author and the sync script. The author never touches code — they control behavior through metadata.
3. Folder sets defaults, YAML overrides
Each source folder has its own defaults (output type, access level). YAML frontmatter in a specific file can override any default. This is convention over configuration: drop a file into Blog/ and it becomes a public page with no extra setup. But you can add access: authorized if needed.
4. Three output formats
Standalone page (type: page) — a full HTML page. Works without a SPA, indexable by search engines. For public content, landing pages, blog posts.
Fragment (type: fragment) — JSON with { title, html }. A SPA component loads and renders it inside a layout. For content embedded in an app (courses, docs with sidebar navigation).
Structured data (type: data) — markdown tables parsed into JSON arrays. For lists, catalogs, schedules — any data that’s convenient to maintain as a table in Obsidian.
5. Sync is separate from build
Sync runs locally — it needs access to the vault. Build runs anywhere (CI, Docker) — it doesn’t need the vault, it works with the already-generated files. Sync outputs into the project folder, build picks up from there.
6. Auto-generated manifest
The sync script creates manifest.json listing all content: slug, title, description, type, access, order. Site components use the manifest for navigation, content lists, “next/previous” links. New file in the vault = new entry automatically.
Workflow
Update content
1. Edit the .md in Obsidian
2. Run sync (command, skill, or automation)
3. Deploy
Add new content
1. Create an .md file in the appropriate vault folder
2. Add YAML frontmatter (minimum: slug)
3. Run sync — the file is picked up automatically
Add a new content type
1. Create a folder in the vault
2. Define defaults for this folder in the sync script config
3. If needed — add a new UI component on the site
Components
Sync script — the CMS core
Input:
- Path to the vault
- Mapping: folder → defaults (type, access, output directory)
What it does:
- Scans source folders
- For each .md: parses YAML frontmatter, merges with defaults
- Converts markdown to the target format (HTML, JSON, structured data)
- Generates manifest.json
- Logs: how many files processed, which were skipped, errors
Markdown processor
Converts .md → HTML accounting for Obsidian-specific syntax:
--→—(em dash)- Callout blocks (
> [!note]) - Wikilinks (leave as-is, convert to standard links, or handle custom)
- Code highlighting
Can be any library: marked, remark, markdown-it, pandoc.
Manifest generator
Collects metadata from all processed files into a single JSON:
[
{
"slug": "getting-started",
"title": "Getting Started",
"description": "Quick start",
"type": "page",
"access": "public",
"order": 1
}
]
Site components use the manifest to build navigation, content lists, “next/previous” links.
Table parser (for type: data)
Markdown tables → JSON arrays of objects. Table headers = keys. Each row = object. Useful for: video lists, schedules, catalogs, FAQ.
Adaptation Questions
Before building, the agent asks the user these questions.
Content
- What types of content will be on the site (blog, docs, guides, courses)?
- Which vault folders contain content for the site?
- Is there any gated content? How is access determined?
- What language is the content in?
YAML
- Do you already use YAML frontmatter? Which fields?
- Are there required fields in the vault (LLM, date, type)?
- How should slugs be formed — from the filename or always explicitly in YAML?
Site
- What stack is the site built on (React, Next.js, Astro, Hugo, plain HTML)?
- Do you need a SPA with dynamic loading or are static pages sufficient?
- Where is it hosted (VPS, Vercel, Netlify, GitHub Pages)?
- How does deployment work now?
Workflow
- Who edits content — one person or a team?
- Do you need automatic sync on file changes, or is manual triggering fine?
- Do you need a preview before deployment?
Gotchas and Pitfalls
Vault and project are different worlds
The sync script must run on a machine that has access to the vault. CI/CD usually doesn’t have vault access. Solution: sync locally, commit the results, build in CI. Or: sync as a pre-deploy step.
Filenames vs slug
Obsidian filenames can contain spaces, non-ASCII characters, special symbols. URL slugs cannot. Always use slug in YAML frontmatter. Never rely on the filename as the slug.
Wikilinks in output
Obsidian [[wikilinks]] are meaningless on a website. Options: replace with HTML links (if the target file is also on the site), strip them (leave only the display text), or leave as-is (if content is inside a SPA and wikilinks aren’t visible to users).
Markdown processors don’t understand Obsidian
Standard markdown libraries don’t know about callout blocks, embed syntax (![[file]]), or other Obsidian extensions. You need a preprocessing step that converts Obsidian-specific syntax before passing it to the markdown processor.
Content intertwined with UI
Marketing pages (course syllabus, pricing, CTAs) often mix text with visual components. Automatic sync doesn’t work here. Options: vault file as the source of truth for copywriting (transferred to code manually), or splitting into text and UI parts.
Manifest can go stale
If sync hasn’t run but files have changed — the manifest is out of date. Solution: always run sync before each deploy, or add a check for “are there unsynced files.”
Scaling
The pattern works for:
- Blog —
Blog/→ standalone HTML pages - Docs —
Docs/→ fragments inside a SPA with navigation - Courses —
Course/→ fragments with access control - Landing pages —
Landing/→ standalone HTML - Changelog —
Changelog/→ structured data or pages - Video library —
Videos/→ structured data (tables → JSON) - FAQ —
FAQ/→ structured data - Email newsletters —
Emails/→ source of truth for copywriting (manual sync)
For each new type: a folder in the vault + defaults in the sync config + (optionally) a UI component.
Reference Implementation
Obsidian + any web framework + sync script in any language.
| Component | Implementation |
|---|---|
| Vault | Obsidian vault with folders organized by content type |
| YAML frontmatter | slug, type, access, title, description, order |
| Sync script | Script in any language: reads vault, parses YAML, converts markdown, generates manifest |
| Markdown processor | marked, remark, markdown-it, pandoc — any library with extensions |
| Manifest | manifest.json — array of objects with metadata for all pages |
| Site | Any framework (React, Astro, Next.js, Hugo) or plain HTML |
| Deploy | Manual, CI/CD, or triggered by an agent command |
Typical result: from 10-30 .md files in the vault you get a full site with navigation, SEO, and content updates without restarting the framework.
Output Artifacts
After building, the user should have:
| Artifact | Description | How to verify |
|---|---|---|
| Content folders in vault | Organized by type | Folders exist, files have YAML frontmatter |
| Sync script | Reads vault, generates output | Running it processes all files without errors |
| manifest.json | Index of all content | Contains an entry for every .md file |
| Output files | HTML/JSON in the project folder | Files exist, content is correct |
| Deploy command | A way to publish to the site | Deploy works, content appears on site |
| Skill or command | A way to run sync with one command | “Update site” → sync + deploy |