Notion Content Pipeline

Two-way markdown ↔ Notion sync for blog and content workflows. Use when: pushing local .md files to Notion for editing, pulling Notion edits back to local files, managing a content pipeline database with statuses (Seed → Draft → Review → Published), or tracking local file ↔ Notion page mappings. Supports batch push-all, per-file push/pull, and pipeline DB creation with Platform/Status/Hook properties.

Audits

Pending

Install

openclaw skills install notion-content-pipeline

notion-content-pipeline

Push local .md files to Notion and pull edits back. Track a content pipeline DB with statuses (Seed → Draft → Review → Published).

Scripts

  • scripts/notion_content_sync.py — push/pull individual files or all at once
  • scripts/create_pipeline_db.py — create a Notion database for content pipeline tracking
  • scripts/pipeline_advance.pyfull round-trip advance: pull → humanize → fact-check → push → status update

Configuration

NOTION_API_KEY=secret_...        # Notion integration token
NOTION_PARENT_PAGE_ID=<uuid>     # Parent page ID for sandbox + pipeline DB
NOTION_SYNC_MAP=~/.notion_sync_map.json  # Where to store file ↔ page ID mapping
CONTENT_DIR=./content            # Directory containing .md files

Or pass --sandbox-id and --sync-map as CLI flags.

Quick start

# Push a single file to Notion
python3 scripts/notion_content_sync.py push content/my-post.md

# Push all .md files in content/
python3 scripts/notion_content_sync.py push-all

# Pull Notion edits back to local file
python3 scripts/notion_content_sync.py pull content/my-post.md

# List all tracked pages with Notion URLs
python3 scripts/notion_content_sync.py list

# Create the Content Pipeline database
python3 scripts/create_pipeline_db.py

Sync map

File ↔ Notion page ID mapping stored in JSON. Example:

{
  "content/my-post.md": "abc123-...",
  "content/other-post.md": "def456-..."
}

Default location: ./notion_sync_map.json (override with NOTION_SYNC_MAP env var).

Overwrite behaviour

On push, if the file is already tracked, the existing Notion page is archived and a fresh page is created. This avoids block-count drift from repeated pushes.

Use --no-overwrite to skip if already pushed.

Pipeline DB schema

Created by create_pipeline_db.py:

  • Title — post title
  • Platform — Blog / LinkedIn / Both
  • Status — Seed → Draft → Humanized → In Review → Published
  • Hook — one-sentence pitch
  • Est. Read Time — < 1 min / 2-3 min / 5-7 min / 10+ min
  • Published URL — final URL once live
  • Source — where the idea came from

pipeline_advance.py — automated round-trip

# Full advance: pull → humanize → fact-check → push → status
python3 scripts/pipeline_advance.py advance content/my-post.md

# Skip steps individually
python3 scripts/pipeline_advance.py advance content/my-post.md --skip-humanize
python3 scripts/pipeline_advance.py advance content/my-post.md --skip-factcheck

# Preview without making changes
python3 scripts/pipeline_advance.py advance content/my-post.md --dry-run

What it does

  1. Pulls Notion edits back to your local .md file
  2. Humanizes — applies mechanical AI-pattern fixes in Python (em dash → comma, "utilize" → "use", filler openers, copula avoidance, curly quotes). Flags rule-of-three and AI-vocab patterns for LLM review. Writes a .humanizer.diff alongside the file.
  3. Fact-checks — runs skills/fact-checker/scripts/fact_check.py if available; otherwise skips gracefully. Report saved as .factcheck.txt.
  4. Pushes the humanized file back to Notion (archives old page, creates fresh one).
  5. Updates status in the Content Pipeline DB:
    • DraftHumanized (after humanize + fact-check)
    • HumanizedIn Review (after fact-check only)
    • Never advances past In Review — that's Nissan's decision.

Extra env var

NOTION_PIPELINE_DB_ID=312c9a82-0734-81bd-81a6-e58d0365e404  # optional, hardcoded fallback

See also

  • references/api_notes.md — Notion API quirks and block type reference