Agent skill
sync-notion-pages
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/sync-notion-pages
SKILL.md
/sync-notion-pages
Bidirectional sync between Obsidian notes and Notion pages for collaborative planning.
Usage
/sync-notion-pages # Check all tracked pages for changes
/sync-notion-pages <note> # Sync a specific note
/sync-notion-pages --push <note> # Force push to Notion
/sync-notion-pages --pull <note> # Force pull from Notion
/sync-notion-pages --link <note> <url> # Link note to Notion page
/sync-notion-pages --unlink <note> # Remove sync tracking from note
/sync-notion-pages --status # Show sync status dashboard
Prerequisites
- Notion MCP server must be connected (tools like
mcp__MCP_DOCKER__API-retrieve-a-page) - Notes must have
notionPageIdin frontmatter to be tracked
Instructions
Phase 1: Parse Command
-
Identify operation mode:
- No args: Check all tracked pages
<note>: Sync specific note (detect direction automatically)--push <note>: Force push local → Notion--pull <note>: Force pull Notion → local--link <note> <url>: Link existing note to Notion page--unlink <note>: Remove sync tracking--status: Show dashboard
-
If note specified, find it:
- Search by filename or title
- Verify note exists
- Check if tracked (has
notionPageId)
Phase 2: Load Manifest
Read .claude/sync/notion-pages-manifest.json:
{
"version": "1.0",
"lastFullSync": "2026-01-20T14:00:00Z",
"trackedPages": {
"<pageId>": {
"localFile": "Trip - Lisbon March 2026.md",
"notionUrl": "https://...",
"notionLastEdited": "2026-01-20T14:30:00Z",
"localLastModified": "2026-01-20T15:00:00Z",
"lastSynced": "2026-01-20T14:00:00Z",
"syncStatus": "local-ahead"
}
}
}
Phase 3: Operations
3A: Link Operation (--link)
-
Parse Notion URL to extract page ID:
- Format:
https://www.notion.so/Page-Title-<pageId>orhttps://www.notion.so/<pageId> - Page ID is typically 32 characters with optional hyphens
- Format:
-
Fetch page from Notion using
mcp__MCP_DOCKER__API-retrieve-a-page:page_id: "<extracted-page-id>" -
Add sync fields to note frontmatter:
yamlnotionPageId: "<pageId>" notionUrl: "<full-url>" lastSynced: null syncStatus: unsynced -
Add entry to manifest
-
Report success
3B: Status Operation (--status)
-
For each tracked page in manifest:
- Fetch Notion page
last_edited_time - Get local file modified timestamp
- Calculate sync status
- Fetch Notion page
-
Display dashboard:
markdown# Notion Page Sync Status | Note | Status | Local Modified | Notion Modified | Last Synced | | ------------- | ----------- | ---------------- | ---------------- | ---------------- | | Trip - Lisbon | local-ahead | 2026-01-21 10:00 | 2026-01-20 14:30 | 2026-01-20 14:00 | -
Summarise:
- X pages synced
- X pages local-ahead (need push)
- X pages remote-ahead (need pull)
- X pages in conflict
3C: Check/Sync Operation (default or specific note)
For each tracked page (or specific note):
-
Fetch Notion metadata:
mcp__MCP_DOCKER__API-retrieve-a-page with page_idExtract
last_edited_time -
Get local file modified time:
- Read file and check
modifiedin frontmatter - Also check filesystem modified time as backup
- Read file and check
-
Compare against lastSynced:
localChanged = localModified > lastSynced remoteChanged = notionLastEdited > lastSynced Status: - synced: !localChanged && !remoteChanged - local-ahead: localChanged && !remoteChanged - remote-ahead: !localChanged && remoteChanged - conflict: localChanged && remoteChanged -
Take action based on status:
synced: Report "No changes"local-ahead: Auto-push to Notionremote-ahead: Auto-pull from Notionconflict: Prompt user for resolution
3D: Push Operation (--push or auto-push)
-
Read local note content:
- Parse frontmatter
- Extract markdown body (skip frontmatter and callouts)
-
Convert Markdown → Notion blocks:
Markdown Notion Block Type # H1heading_1 ## H2heading_2 ### H3heading_3 paragraphparagraph - itembulleted_list_item 1. itemnumbered_list_item - [ ] taskto_do (checked: false) - [x] taskto_do (checked: true) > quotequote codecode ` table -
Clear existing Notion blocks:
- Get current blocks:
mcp__MCP_DOCKER__API-get-block-children - Delete each block:
mcp__MCP_DOCKER__API-delete-a-block
- Get current blocks:
-
Append new blocks:
- Use
mcp__MCP_DOCKER__API-patch-block-childrento add converted blocks
- Use
-
Update timestamps:
- Update frontmatter
lastSyncedto now - Update manifest with new sync time
- Set
syncStatus: synced
- Update frontmatter
3E: Pull Operation (--pull or auto-pull)
-
Fetch Notion page content:
mcp__MCP_DOCKER__API-retrieve-a-page with page_id mcp__MCP_DOCKER__API-get-block-children with block_id=page_id -
Convert Notion blocks → Markdown:
For each block, convert to markdown:
javascriptfunction blockToMarkdown(block) { switch (block.type) { case "heading_1": return `# ${getRichText(block.heading_1.rich_text)}`; case "heading_2": return `## ${getRichText(block.heading_2.rich_text)}`; case "heading_3": return `### ${getRichText(block.heading_3.rich_text)}`; case "paragraph": return getRichText(block.paragraph.rich_text); case "bulleted_list_item": return `- ${getRichText(block.bulleted_list_item.rich_text)}`; case "numbered_list_item": return `1. ${getRichText(block.numbered_list_item.rich_text)}`; case "to_do": const checked = block.to_do.checked ? "x" : " "; return `- [${checked}] ${getRichText(block.to_do.rich_text)}`; case "quote": return `> ${getRichText(block.quote.rich_text)}`; case "code": return `\`\`\`${block.code.language}\n${getRichText(block.code.rich_text)}\n\`\`\``; case "divider": return "---"; default: return ""; } } function getRichText(richTextArray) { return richTextArray.map((t) => t.plain_text).join(""); } -
Preserve frontmatter:
- Read current note frontmatter
- Update
modifiedto today - Update
lastSyncedto now - Set
syncStatus: synced
-
Preserve local-only sections:
- Keep callout blocks (e.g.,
> [!info]) - Keep frontmatter
- Replace body content
- Keep callout blocks (e.g.,
-
Write updated note
-
Update manifest
Phase 4: Conflict Resolution
When both sides changed since last sync:
-
Show diff summary:
markdown## Conflict Detected: Trip - Lisbon March 2026 **Local changes since 2026-01-20 14:00:** - Modified flights section - Added new activities **Notion changes since 2026-01-20 14:00:** - Collaborator added restaurant suggestions - Updated accommodation notes -
Offer resolution options using AskUserQuestion:
- Keep local (push to Notion)
- Keep remote (pull from Notion)
- Skip (resolve manually later)
-
Execute chosen resolution:
- If keep local: Run push operation
- If keep remote: Run pull operation
- If skip: Leave status as
conflict
Phase 5: Report
# Notion Page Sync Report
**Synced at:** 2026-01-21T10:30:00Z
## Summary
| Action | Count |
| ------------------- | ----- |
| Synced (no changes) | 2 |
| Pushed to Notion | 1 |
| Pulled from Notion | 0 |
| Conflicts | 0 |
| Errors | 0 |
## Details
### Pushed: Trip - Lisbon March 2026
- Local modified: 2026-01-21 10:00
- Notion updated: 2026-01-21 10:30
## Next Steps
- Review synced content in Notion
- Check for any formatting issues
Content Preservation Rules
Always Preserve in Local Note:
- Frontmatter (sync adds/updates specific fields)
- Local callout blocks (
> [!info],> [!warning]) - Links to other Obsidian notes (
[[Note Name]])
Always Push to Notion:
- Headings
- Paragraphs
- Lists (bulleted, numbered, tasks)
- Tables
- Code blocks
Handle Specially:
- Wiki-links: Convert
[[Note]]to plain text when pushing - Callouts: Skip when pushing (Notion doesn't support same format)
- Embedded content: Skip (handle manually)
Error Handling
| Error | Action |
|---|---|
| Notion API rate limited | Wait and retry |
| Page not found | Remove from tracking, notify user |
| Invalid page ID | Report error, suggest re-linking |
| Network timeout | Retry once, then report |
| Malformed blocks | Skip block, continue, report at end |
Manifest Location
.claude/sync/notion-pages-manifest.json
This is separate from the Confluence sync manifest at .claude/sync/manifest.json.
Example Workflow
First-time setup for a note:
User: /sync-notion-pages --link "Trip - Lisbon March 2026" https://www.notion.so/Lisbon-March-2026-2ee76da238c981459c88ca451e112c39
Claude:
1. Extracts page ID: 2ee76da238c981459c88ca451e112c39
2. Fetches Notion page to verify
3. Updates note frontmatter with sync fields
4. Adds to manifest
5. Reports: "Linked Trip - Lisbon March 2026 to Notion page"
Regular sync check:
User: /sync-notion-pages
Claude:
1. Loads manifest
2. For each tracked page, checks timestamps
3. Reports status for all pages
4. Offers to sync any that need it
Force push after local edits:
User: /sync-notion-pages --push "Trip - Lisbon March 2026"
Claude:
1. Reads local note
2. Converts to Notion blocks
3. Updates Notion page
4. Updates timestamps
5. Reports success
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?