Claude-code-plugins-plus-skills notion-content-management
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/notion-pack/skills/notion-content-management" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-notion-content-management && rm -rf "$T"
manifest:
plugins/saas-packs/notion-pack/skills/notion-content-management/SKILL.mdsource content
Notion Content Management
Overview
Complete guide to creating, updating, archiving, and composing Notion pages and block content using the
@notionhq/client SDK. Covers page lifecycle, all common block types, rich text formatting, and bulk content operations.
Prerequisites
- Completed
setupnotion-install-auth
environment variable setNOTION_TOKEN- Target database or page shared with your integration (via Connections menu)
v2+ installed (TypeScript) or@notionhq/client
(Python)notion-client
Instructions
Step 1: Create, Update, and Archive Pages
Create a page in a database with typed properties and initial block content:
import { Client } from '@notionhq/client'; const notion = new Client({ auth: process.env.NOTION_TOKEN }); // Create a page with properties and inline content async function createPage(databaseId: string) { const page = await notion.pages.create({ parent: { database_id: databaseId }, icon: { emoji: '📄' }, cover: { external: { url: 'https://images.unsplash.com/photo-cover-id' }, }, properties: { // Title property (required for database pages) Name: { title: [{ text: { content: 'Q1 Sprint Retrospective' } }], }, Status: { select: { name: 'In Progress' }, }, Priority: { select: { name: 'High' }, }, Tags: { multi_select: [{ name: 'Engineering' }, { name: 'Sprint' }], }, 'Due Date': { date: { start: '2026-04-01', end: '2026-04-05' }, }, Assignee: { people: [{ id: 'user-uuid-here' }], }, Effort: { number: 8, }, Done: { checkbox: false, }, URL: { url: 'https://example.com/sprint-board', }, }, // Initial page body (block children) children: [ { heading_2: { rich_text: [{ text: { content: 'Summary' } }], }, }, { paragraph: { rich_text: [{ text: { content: 'This page tracks the Q1 sprint retrospective.' } }], }, }, ], }); console.log('Created page:', page.id); return page; }
Update page properties after creation:
async function updatePageProperties(pageId: string) { const updated = await notion.pages.update({ page_id: pageId, properties: { Status: { select: { name: 'Done' } }, Done: { checkbox: true }, // Clear a property by setting to null 'Due Date': { date: null }, }, // Update icon/cover icon: { emoji: '✅' }, }); console.log('Updated page:', updated.id); return updated; }
Archive and restore pages:
// Archive (soft-delete) async function archivePage(pageId: string) { await notion.pages.update({ page_id: pageId, archived: true }); console.log('Archived page:', pageId); } // Restore from archive async function restorePage(pageId: string) { await notion.pages.update({ page_id: pageId, archived: false }); console.log('Restored page:', pageId); }
Step 2: Compose Content with Block Types
Append blocks to an existing page. Each block type has its own shape:
async function appendBlocks(pageId: string) { await notion.blocks.children.append({ block_id: pageId, children: [ // Headings (heading_1, heading_2, heading_3) { heading_1: { rich_text: [{ text: { content: 'Project Overview' } }], is_toggleable: false, }, }, // Paragraph with rich text formatting { paragraph: { rich_text: [ { text: { content: 'This is ' } }, { text: { content: 'bold text' }, annotations: { bold: true } }, { text: { content: ' and ' } }, { text: { content: 'inline code' }, annotations: { code: true } }, { text: { content: '. Visit ' } }, { text: { content: 'our docs', link: { url: 'https://example.com' } }, annotations: { italic: true }, }, { text: { content: '.' } }, ], }, }, // Bulleted list items { bulleted_list_item: { rich_text: [{ text: { content: 'First bullet point' } }], }, }, { bulleted_list_item: { rich_text: [{ text: { content: 'Second bullet point' } }], }, }, // Numbered list items { numbered_list_item: { rich_text: [{ text: { content: 'Step one' } }], }, }, { numbered_list_item: { rich_text: [{ text: { content: 'Step two' } }], }, }, // To-do items { to_do: { rich_text: [{ text: { content: 'Review pull requests' } }], checked: false, }, }, { to_do: { rich_text: [{ text: { content: 'Update documentation' } }], checked: true, }, }, // Toggle block (collapsible) { toggle: { rich_text: [{ text: { content: 'Click to expand details' } }], children: [ { paragraph: { rich_text: [{ text: { content: 'Hidden content inside toggle.' } }], }, }, ], }, }, // Code block { code: { rich_text: [{ text: { content: 'const x = 42;\nconsole.log(x);' } }], language: 'typescript', caption: [{ text: { content: 'Example snippet' } }], }, }, // Callout { callout: { rich_text: [{ text: { content: 'Important: review before merging.' } }], icon: { emoji: '⚠️' }, color: 'yellow_background', }, }, // Quote { quote: { rich_text: [{ text: { content: 'Ship early, ship often.' } }], color: 'gray', }, }, // Divider { divider: {} }, // Image (external URL) { image: { external: { url: 'https://example.com/diagram.png' }, caption: [{ text: { content: 'System architecture diagram' } }], }, }, // Table (3 columns x 2 rows) { table: { table_width: 3, has_column_header: true, has_row_header: false, children: [ { table_row: { cells: [ [{ text: { content: 'Feature' } }], [{ text: { content: 'Status' } }], [{ text: { content: 'Owner' } }], ], }, }, { table_row: { cells: [ [{ text: { content: 'Auth' } }], [{ text: { content: 'Done' } }], [{ text: { content: 'Alice' } }], ], }, }, ], }, }, ], }); console.log('Blocks appended to page:', pageId); }
Step 3: Update and Delete Individual Blocks
Retrieve, modify, and remove specific blocks:
// List all child blocks of a page async function listBlocks(pageId: string) { const blocks: any[] = []; let cursor: string | undefined; do { const response = await notion.blocks.children.list({ block_id: pageId, start_cursor: cursor, page_size: 100, }); blocks.push(...response.results); cursor = response.has_more ? response.next_cursor! : undefined; } while (cursor); return blocks; } // Update a specific block's content async function updateBlock(blockId: string) { await notion.blocks.update({ block_id: blockId, paragraph: { rich_text: [ { text: { content: 'Updated paragraph content with ' } }, { text: { content: 'new formatting' }, annotations: { bold: true, color: 'red' } }, ], }, }); console.log('Block updated:', blockId); } // Update a to-do block's checked state async function toggleTodo(blockId: string, checked: boolean) { await notion.blocks.update({ block_id: blockId, to_do: { checked, }, }); } // Delete a block (moves to trash, recoverable for 30 days) async function deleteBlock(blockId: string) { await notion.blocks.delete({ block_id: blockId }); console.log('Deleted block:', blockId); } // Retrieve a single block by ID async function getBlock(blockId: string) { const block = await notion.blocks.retrieve({ block_id: blockId }); console.log('Block type:', block.type, 'Has children:', block.has_children); return block; }
Output
- Created pages with typed properties, icons, covers, and initial block content
- Updated page properties and metadata
- Archived and restored pages
- Appended all common block types: headings, paragraphs, lists, to-dos, toggles, code, callouts, quotes, dividers, images, and tables
- Retrieved, updated, and deleted individual blocks
Error Handling
| Error | Cause | Solution |
|---|---|---|
(400) | Wrong property type or name | Retrieve database schema with to confirm property names and types |
(404) | Page/block not shared with integration | Open the page in Notion, click > Connections > add your integration |
(401) | Invalid or expired token | Regenerate at and update |
(429) | Over 3 requests/second | Implement exponential backoff; read header |
(409) | Concurrent edit to same block | Retry with fresh block data from |
(413) | Over 100 blocks in one append | Batch into chunks of 100 blocks per call |
Examples
Complete Page Builder
import { Client } from '@notionhq/client'; const notion = new Client({ auth: process.env.NOTION_TOKEN }); async function buildMeetingNotes(databaseId: string) { // 1. Create the page const page = await notion.pages.create({ parent: { database_id: databaseId }, icon: { emoji: '📝' }, properties: { Name: { title: [{ text: { content: `Standup ${new Date().toISOString().slice(0, 10)}` } }] }, Status: { select: { name: 'In Progress' } }, Tags: { multi_select: [{ name: 'Standup' }, { name: 'Daily' }] }, }, }); // 2. Append structured content await notion.blocks.children.append({ block_id: page.id, children: [ { heading_2: { rich_text: [{ text: { content: 'Yesterday' } }] } }, { bulleted_list_item: { rich_text: [{ text: { content: 'Completed auth integration' } }] } }, { bulleted_list_item: { rich_text: [{ text: { content: 'Fixed rate-limit retry logic' } }] } }, { heading_2: { rich_text: [{ text: { content: 'Today' } }] } }, { to_do: { rich_text: [{ text: { content: 'Build content management module' } }], checked: false } }, { to_do: { rich_text: [{ text: { content: 'Write integration tests' } }], checked: false } }, { heading_2: { rich_text: [{ text: { content: 'Blockers' } }] } }, { callout: { rich_text: [{ text: { content: 'Waiting on API key for staging environment.' } }], icon: { emoji: '🚧' }, color: 'red_background', }, }, ], }); console.log('Meeting notes page:', `https://notion.so/${page.id.replace(/-/g, '')}`); return page; }
Python Example
import os from notion_client import Client notion = Client(auth=os.environ["NOTION_TOKEN"]) # Create a page page = notion.pages.create( parent={"database_id": "your-database-id"}, properties={ "Name": {"title": [{"text": {"content": "Python Page"}}]}, "Status": {"select": {"name": "Draft"}}, "Tags": {"multi_select": [{"name": "API"}, {"name": "Python"}]}, }, ) print(f"Created: {page['id']}") # Update properties notion.pages.update( page_id=page["id"], properties={ "Status": {"select": {"name": "Done"}}, }, ) # Append blocks notion.blocks.children.append( block_id=page["id"], children=[ {"heading_2": {"rich_text": [{"text": {"content": "Notes"}}]}}, { "paragraph": { "rich_text": [ {"text": {"content": "Created via "}}, {"text": {"content": "Python SDK"}, "annotations": {"bold": True}}, ] } }, { "code": { "rich_text": [{"text": {"content": "print('hello notion')"}}], "language": "python", } }, {"divider": {}}, { "to_do": { "rich_text": [{"text": {"content": "Review and publish"}}], "checked": False, } }, ], ) # Archive the page notion.pages.update(page_id=page["id"], archived=True)
Batch Block Append (Chunked for >100 Blocks)
async function appendBlocksChunked( pageId: string, blocks: any[], chunkSize = 100, ) { for (let i = 0; i < blocks.length; i += chunkSize) { const chunk = blocks.slice(i, i + chunkSize); await notion.blocks.children.append({ block_id: pageId, children: chunk, }); // Respect rate limits between chunks if (i + chunkSize < blocks.length) { await new Promise((r) => setTimeout(r, 350)); } } }
Resources
- Working with Page Content
- Create a Page
- Update Page Properties
- Append Block Children
- Block Type Reference
- Rich Text Object
- @notionhq/client npm
- notion-sdk-py GitHub
Next Steps
Proceed to
notion-data-handling for database queries, filtering, sorting, and pagination patterns.