notion-content-management

Create, update, archive, and compose Notion pages and block content. Use when building pages programmatically, appending rich content blocks, updating page properties, or managing page lifecycle (archive/restore). Trigger with phrases like "notion create page", "notion add blocks", "notion update page", "notion archive page", "notion content", "notion block types", "notion rich text".

1,868 stars

Best use case

notion-content-management is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create, update, archive, and compose Notion pages and block content. Use when building pages programmatically, appending rich content blocks, updating page properties, or managing page lifecycle (archive/restore). Trigger with phrases like "notion create page", "notion add blocks", "notion update page", "notion archive page", "notion content", "notion block types", "notion rich text".

Teams using notion-content-management should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/notion-content-management/SKILL.md --create-dirs "https://raw.githubusercontent.com/jeremylongshore/claude-code-plugins-plus-skills/main/plugins/saas-packs/notion-pack/skills/notion-content-management/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/notion-content-management/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How notion-content-management Compares

Feature / Agentnotion-content-managementStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create, update, archive, and compose Notion pages and block content. Use when building pages programmatically, appending rich content blocks, updating page properties, or managing page lifecycle (archive/restore). Trigger with phrases like "notion create page", "notion add blocks", "notion update page", "notion archive page", "notion content", "notion block types", "notion rich text".

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# 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 `notion-install-auth` setup
- `NOTION_TOKEN` environment variable set
- Target database or page shared with your integration (via Connections menu)
- `@notionhq/client` v2+ installed (TypeScript) or `notion-client` (Python)

## Instructions

### Step 1: Create, Update, and Archive Pages

Create a page in a database with typed properties and initial block content:

```typescript
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:

```typescript
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:

```typescript
// 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:

```typescript
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:

```typescript
// 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 |
|-------|-------|----------|
| `validation_error` (400) | Wrong property type or name | Retrieve database schema with `databases.retrieve()` to confirm property names and types |
| `object_not_found` (404) | Page/block not shared with integration | Open the page in Notion, click `...` > Connections > add your integration |
| `unauthorized` (401) | Invalid or expired token | Regenerate at `notion.so/my-integrations` and update `NOTION_TOKEN` |
| `rate_limited` (429) | Over 3 requests/second | Implement exponential backoff; read `Retry-After` header |
| `conflict_error` (409) | Concurrent edit to same block | Retry with fresh block data from `blocks.retrieve()` |
| `body too large` (413) | Over 100 blocks in one append | Batch into chunks of 100 blocks per `blocks.children.append` call |

## Examples

### Complete Page Builder
```typescript
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
```python
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)
```typescript
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](https://developers.notion.com/docs/working-with-page-content)
- [Create a Page](https://developers.notion.com/reference/post-page)
- [Update Page Properties](https://developers.notion.com/reference/patch-page)
- [Append Block Children](https://developers.notion.com/reference/patch-block-children)
- [Block Type Reference](https://developers.notion.com/reference/block)
- [Rich Text Object](https://developers.notion.com/reference/rich-text)
- [@notionhq/client npm](https://www.npmjs.com/package/@notionhq/client)
- [notion-sdk-py GitHub](https://github.com/ramnes/notion-sdk-py)

## Next Steps
Proceed to `notion-data-handling` for database queries, filtering, sorting, and pagination patterns.

Related Skills

windsurf-license-management

1868
from jeremylongshore/claude-code-plugins-plus-skills

Manage Windsurf licenses and seat allocation. Activate when users mention "license management", "seat allocation", "billing optimization", "user licenses", or "subscription management". Handles license administration. Use when working with windsurf license management functionality. Trigger with phrases like "windsurf license management", "windsurf management", "windsurf".

windsurf-dependency-management

1868
from jeremylongshore/claude-code-plugins-plus-skills

Analyze and update dependencies with vulnerability scanning. Activate when users mention "update dependencies", "security audit", "npm audit", "vulnerability scan", or "dependency updates". Handles dependency analysis and updates. Use when working with windsurf dependency management functionality. Trigger with phrases like "windsurf dependency management", "windsurf management", "windsurf".

sentry-release-management

1868
from jeremylongshore/claude-code-plugins-plus-skills

Manage Sentry releases with versioning, commit association, and source map uploads. Use when creating releases, linking commits to errors, uploading release artifacts, monitoring release health, or cleaning up old releases. Trigger with phrases like "sentry release", "create sentry version", "sentry source maps", "sentry suspect commits", "release health".

notion-webhooks-events

1868
from jeremylongshore/claude-code-plugins-plus-skills

Build change detection and event handling for Notion workspaces using polling, native webhooks, and third-party connectors. Use when implementing real-time sync, change feeds, incremental backup, or event-driven workflows with Notion data. Trigger with phrases like "notion webhook", "notion events", "notion change detection", "notion polling", "notion sync changes", "notion real-time", "notion watch for changes".

notion-upgrade-migration

1868
from jeremylongshore/claude-code-plugins-plus-skills

Upgrade @notionhq/client SDK versions and migrate between Notion API versions. Use when updating SDK packages, handling breaking changes between API versions, adopting new SDK features like comments API or status properties, or migrating Python notion-client. Trigger with phrases like "upgrade notion SDK", "notion migration", "notion breaking changes", "update notionhq client", "notion API version upgrade", "notion deprecation".

notion-security-basics

1868
from jeremylongshore/claude-code-plugins-plus-skills

Apply Notion API security best practices for integration tokens, OAuth2 flows, least-privilege capabilities, and page-level access control. Use when securing integration tokens, configuring OAuth2 for public integrations, rotating credentials, or auditing which pages an integration can access. Trigger with phrases like "notion security", "notion secrets", "secure notion", "notion API key security", "notion token rotation", "notion OAuth2", "notion permissions audit".

notion-search-retrieve

1868
from jeremylongshore/claude-code-plugins-plus-skills

Search Notion workspaces and retrieve pages, databases, and block content using the Notion API. Use when querying databases with filters, searching across a workspace, paginating large result sets, or extracting page content. Trigger with phrases like "notion search", "query notion database", "notion retrieve page", "notion pagination", "notion filter", "notion blocks", "notion get content".

notion-sdk-patterns

1868
from jeremylongshore/claude-code-plugins-plus-skills

Apply production-ready @notionhq/client SDK patterns for TypeScript and Python. Use when implementing Notion integrations, building database queries with filters and sorts, handling pagination, constructing rich text blocks, or establishing team coding standards for Notion API usage. Trigger with "notion SDK patterns", "notion best practices", "notion code patterns", "idiomatic notion", "notion typescript", "notion python SDK".

notion-reliability-patterns

1868
from jeremylongshore/claude-code-plugins-plus-skills

Graceful degradation when Notion is down: offline cache, retry with exponential backoff, circuit breaker, health checks, and fallback content. Use when building fault-tolerant Notion integrations for production. Trigger with phrases like "notion reliability", "notion circuit breaker", "notion offline fallback", "notion health check", "notion graceful degradation".

notion-reference-architecture

1868
from jeremylongshore/claude-code-plugins-plus-skills

Design and implement a production-ready Notion integration architecture with proper layering, caching, error handling, and testing strategies. Use when designing new Notion integrations, reviewing existing project structure, establishing architecture standards for Notion applications, or migrating from ad-hoc API calls to a layered architecture. Trigger: "notion architecture", "notion project structure", "notion reference architecture", "notion integration design", "notion layered architecture", "notion service pattern".

notion-rate-limits

1868
from jeremylongshore/claude-code-plugins-plus-skills

Manage Notion API rate limits with exponential backoff, queue-based throttling, and batch optimization. Use when hitting 429 errors, implementing retry logic, or optimizing API request throughput for Notion integrations. Trigger with "notion rate limit", "notion 429", "notion retry", "notion backoff", "notion throttling", "notion too many requests", "notion queue".

notion-prod-checklist

1868
from jeremylongshore/claude-code-plugins-plus-skills

Execute Notion API production deployment checklist and readiness verification. Use when deploying Notion integrations to production, preparing for launch, verifying go-live readiness, or auditing an existing Notion integration. Trigger: "notion production checklist", "deploy notion integration", "notion go-live", "notion launch readiness", "notion prod audit".