Skip to main content
← Back to Blog
#writing#workflow#Notion#Obsidian#CMS

Private-first blog workflow: Notion → Obsidian → CMS

·8 min read

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

  1. Capture and organize ideas in Notion.
  2. Draft in Notion’s database entry.
  3. Export or sync to Obsidian for offline polishing.
  4. Finalize markdown, frontmatter, images, and SEO metadata in Obsidian.
  5. Hand off polished markdown to the CMS via paste, API, or SSG pipeline.
  6. 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)

  1. Broken image references — keep images in /assets/{{slug}}/ and reference by that path.
  2. Duplicate slugs — generate in Notion and validate on export.
  3. Lost metadata — require frontmatter via Templater.
  4. Over-automation — automate only after conventions are stable.
  5. 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

  1. Upload markdown + images to CMS draft.
  2. Use CMS preview to verify layout and shortcodes.
  3. Accessibility quick check: alt text, heading order.
  4. 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.


Try TextPro

Download the app and get started today.

Download on App Store