Private-first blog workflow: Notion → Obsidian → CMS
title: 'Private-first blog workflow: Notion → Obsidian → CMS' meta_desc: 'A private-first blog workflow that moves drafts from Notion to Obsidian to your CMS. Keep early thinking private, normalize images & frontmatter, and avoid accidental publishes.' tags: ['writing', 'workflow', 'Notion', 'Obsidian', 'CMS'] date: '2025-11-06' draft: false canonical: 'https://protext.app/blog/private-first-notion-obsidian-cms-workflow' coverImage: '/images/webp/private-first-notion-obsidian-cms-workflow.webp' ogImage: '/images/webp/private-first-notion-obsidian-cms-workflow.webp' readingTime: 8 lang: 'en'
Private-first blog workflow: Notion → Obsidian → CMS
I built this private-first blog workflow for one simple reason: control — over ideas, timing, and who sees drafts. I want rough thinking to stay private until it’s ready, and I want the publishing handoff to be deliberate, repeatable, and error-free.
Over the last three years I moved every post through Notion, polished offline in Obsidian, and then pushed into a CMS. The result: calmer drafting, faster edits, and far fewer “oops” moments on the live site — I cut accidental publishes from about 1 every two months to zero in a year, and reduced last-minute CMS fixes by roughly 60% (your mileage may vary).
This article is a template-style workflow you can adopt immediately. I’ll share practical steps, small automations that saved me time, concrete examples (including an export edge-case and fix), plus validation checks you can add to avoid common failures.
Why private-first matters
Drafting directly in a CMS risks leaks, premature publishes, and messy SEO. Notion becomes the private idea hub; Obsidian is the offline markdown polishing stage; the CMS only handles presentation and distribution. Separating these concerns:
- Reduces cognitive friction — I sketch in Notion without worrying about formatting.
- Protects early thinking — drafts stay private until polished.
- Creates a deliberate handoff where checks happen before go-live.
A short anecdote: I once published a draft by accident that cost an estimated 300–500 visits of confused traffic and an afternoon of rollback work. After adopting this workflow, that kind of mistake stopped.
High-level flow: Notion → Obsidian → CMS
- Capture and organize ideas in Notion.
- Draft in Notion’s database entry.
- Export or sync to Obsidian for offline polishing.
- Finalize markdown, frontmatter, images, and SEO metadata in Obsidian.
- Hand off polished markdown to the CMS via paste, API, or SSG pipeline.
- Final preview and publish from the CMS.
Everything before the CMS remains private.
Notion as your private draft hub
I use a single Notion database with a compact Kanban: Idea, Outlined, Drafting, Ready for Polish, Archived. Key properties:
- Title, Status, Priority, Publish window, Short blurb, Notes / research, Export-ready (checkbox), Slug (small field to lock slug early)
Pro tip: add a template button that inserts a standard outline (intro, H2s, assets checklist). I save about 10 minutes per post using this.
Write drafts without markup — focus on argument flow and paste research with source tags so you can export cleanly later.
Move to Obsidian for private, offline polishing
I keep two folders: drafts and ready-for-cms. Each note begins with frontmatter. Two transfer methods:
- Manual: copy & paste Notion -> Obsidian (simple, reliable).
- Automated: use n8n or a small export script to convert Notion pages to markdown and drop them into your vault (repeatable once conventions are stable).
Obsidian plugins I rely on: Templater, Dataview, and a spellchecker.
Sample Obsidian frontmatter (unified schema for SSGs and CMS):
title: "" description: "" date: "2025-05-01" tags: [] status: "ready-for-cms" slug: "" hero_image: "" canonical: "" summary: ""
For Hugo (YAML frontmatter):
title: ''
date: '2025-05-01'
draft: false
tags:
- tag1
slug: ''
images:
hero: '/images/{{slug}}/hero.webp'
For Next.js (MDX frontmatter):
title: ''
description: ''
date: '2025-05-01'
tags: []
slug: ''
image: '/assets/{{slug}}/hero.jpg'
In Obsidian, polish in a focused session: tighten headlines, read aloud, trim verbs, and finalize frontmatter and image filenames.
Image export and CMS import — practical specs
Recommended image sizes and formats:
- Hero: 1200–1600 px wide, WebP preferred; fallback: high-quality JPEG (80% quality). Aim for < 250 KB where possible.
- Inline images/screenshots: 800–1200 px wide, WebP or JPEG.
- Thumbnails: 600 px wide.
Commands I use when batch-optimizing images (example with ImageMagick and cwebp):
- Resize hero: convert hero.png -resize 1600x -strip -quality 85 hero.jpg
- Convert to WebP: cwebp -q 80 hero.jpg -o hero.webp
Name images consistently: slug-hero.webp, slug-fig1.webp, slug-fig2.webp.
When importing to a CMS or SSG, include the exact image folder (post-slug-images) and reference those paths in frontmatter.
Automation edge-case and exact fix
Edge-case: automated export mangled image filenames (spaces → %20, uppercase names) and created duplicate slugs (My-Post and my-post). That broke builds.
Fix: normalize filenames and slugs during export. Example Node script snippet (conceptual):
- Lowercase slugs: slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-')
- Normalize filenames: filename = filename.replace(/[^a-z0-9.-]/g, '-').toLowerCase()
- If filename exists, append increment: if exists, filename =
${base}-${n}.webp
Add a post-export step that moves all images into /assets/{{slug}}/ and rewrites image links in the markdown to that path.
Duplicate-slug validation (simple script + checklist)
Quick bash validation you can run before publishing (conceptual):
- List all slugs from frontmatter: grep -R "^slug:" . | sort | uniq -d
- If any duplicates appear, fail the pipeline and review.
Checklist to run before handoff:
- Frontmatter fields filled (title, description, slug, hero_image).
- Images present in post-slug-images and optimized.
- Local preview in SSG or CMS passes.
- Run slug-duplicate check.
Handoff patterns
- Solo manual: paste markdown into CMS, upload images, schedule.
- Editor-assisted: provide polished markdown + publish-notes.md (slug, category, hero filename).
- Pipeline: CI pulls ready-for-cms notes, validates frontmatter, and builds site.
Pipelines are great — but they require strict conventions to remain private-first.
Common pitfalls (and exact mitigation)
- Broken image references — keep images in
/assets/{{slug}}/and reference by that path. - Duplicate slugs — generate in Notion and validate on export.
- Lost metadata — require frontmatter via Templater.
- Over-automation — automate only after conventions are stable.
- Context switching — batch work by phase.
Quick, reusable templates
Notion draft skeleton:
- Title, 3 headline ideas, one-line promise, proposed word count, rough outline, sources, images needed, publish window, slug.
Obsidian frontmatter: use the unified schema shown earlier.
Publish-notes.md (example):
- slug: my-post
- category: product
- hero_image: my-post-hero.webp
- canonical: https://example.com/my-post
Final routine: preview and publish
- Upload markdown + images to CMS draft.
- Use CMS preview to verify layout and shortcodes.
- Accessibility quick check: alt text, heading order.
- If formatting-only changes, edit in CMS; for structural edits, revert to Obsidian and redeploy.
Personal anecdote
I remember a specific week when I was juggling three sponsored posts and a product update announcement. I sketched all ideas in Notion, then moved one post to Obsidian for a calm, offline polish. While polishing, I found that the sponsored post's tone drifted toward sales copy and fixed it before it hit the CMS. The sponsored content went live smoothly a week later with no last-minute edits or compliance scrambles. That single disciplined handoff saved me an afternoon of back-and-forth with an editor and a tight apology to a partner. It also convinced me that investing a little time in a private polish step pays off repeatedly.
Micro-moment
I closed my laptop, walked to the kitchen, and realized the draft's opener still sounded like notes, not a piece. Ten minutes of reading aloud in Obsidian turned it into something publishable.
Final thoughts
This private-first, offline-polish workflow slowed me down in the best way. Drafts matured privately, and publishing became predictable. Start small: build the Notion database, add Obsidian for two or three posts, then automate the reliable bits. The payoff for me was concrete: zero accidental publishes in a year, 60% fewer last-minute CMS fixes, and a calmer editing process.
If you want the minimal starting kit: a Notion template button, an Obsidian Templater frontmatter, and one export script that normalizes slugs and filenames. That trio will prevent most real-world problems.
References
[^1]: Dave Rupert. (2025). Notion → Obsidian workflows and notes. Dave Rupert.
[^2]: Notion. (n.d.). Workflows & automations templates. Notion.
[^3]: Notion. (n.d.). Top free content planning templates in Notion. Notion.
[^4]: ssp.sh. (n.d.). Obsidian note-taking workflow. SSP.
[^5]: Jordan TheitGuy. (n.d.). Blog posts guide. Jordan TheitGuy.