Agent Skills: Notion Core Workflow B — Blocks, Content & Comments

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/notion-core-workflow-b

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/notion-pack/skills/notion-core-workflow-b

Skill Files

Browse the full folder contents for notion-core-workflow-b.

Download Skill

Loading file tree…

plugins/saas-packs/notion-pack/skills/notion-core-workflow-b/SKILL.md

Skill Metadata

Name
notion-core-workflow-b
Description
|

Notion Core Workflow B — Blocks, Content & Comments

Overview

Secondary workflow for content operations: reading block trees, appending content, building rich text with annotations, and managing comments.

Prerequisites

  • Completed notion-install-auth setup
  • A Notion page shared with your integration
  • Familiarity with notion-core-workflow-a (databases/pages)

Instructions

Step 1: Retrieve Block Children

import { Client } from '@notionhq/client';

const notion = new Client({ auth: process.env.NOTION_TOKEN });

async function getPageContent(pageId: string) {
  const blocks = [];
  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 : undefined;
  } while (cursor);

  return blocks;
}

Step 2: Read Blocks Recursively (Nested Content)

async function getBlockTree(blockId: string, depth = 0): Promise<any[]> {
  const blocks = await getPageContent(blockId);
  const tree = [];

  for (const block of blocks) {
    const node: any = { ...block, children: [] };
    // Recursively fetch children if block has them
    if ('has_children' in block && block.has_children) {
      node.children = await getBlockTree(block.id, depth + 1);
    }
    tree.push(node);
  }

  return tree;
}

// Extract plain text from a block tree
function blockToText(block: any): string {
  const type = block.type;
  if (block[type]?.rich_text) {
    return block[type].rich_text.map((t: any) => t.plain_text).join('');
  }
  return '';
}

Step 3: Append Content Blocks

async function appendContent(pageId: string) {
  await notion.blocks.children.append({
    block_id: pageId,
    children: [
      // Heading
      {
        heading_1: {
          rich_text: [{ text: { content: 'Section Title' } }],
        },
      },
      // Paragraph with formatting
      {
        paragraph: {
          rich_text: [
            { text: { content: 'Regular text, ' } },
            { text: { content: 'bold' }, annotations: { bold: true } },
            { text: { content: ', ' } },
            { text: { content: 'italic' }, annotations: { italic: true } },
            { text: { content: ', ' } },
            { text: { content: 'code' }, annotations: { code: true } },
            { text: { content: ', and ' } },
            {
              text: { content: 'a link', link: { url: 'https://notion.so' } },
              annotations: { underline: true },
            },
          ],
        },
      },
      // 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
      {
        numbered_list_item: {
          rich_text: [{ text: { content: 'Step one' } }],
        },
      },
      // To-do items
      {
        to_do: {
          rich_text: [{ text: { content: 'Task to complete' } }],
          checked: false,
        },
      },
      {
        to_do: {
          rich_text: [{ text: { content: 'Already done' } }],
          checked: true,
        },
      },
      // Code block
      {
        code: {
          rich_text: [{ text: { content: 'console.log("Hello Notion!");' } }],
          language: 'typescript',
        },
      },
      // Callout
      {
        callout: {
          rich_text: [{ text: { content: 'Important note here' } }],
          icon: { emoji: '💡' },
        },
      },
      // Quote
      {
        quote: {
          rich_text: [{ text: { content: 'A meaningful quote' } }],
        },
      },
      // Divider
      { divider: {} },
      // Toggle block (with children added separately)
      {
        toggle: {
          rich_text: [{ text: { content: 'Click to expand' } }],
        },
      },
    ],
  });
}

Step 4: Rich Text Annotations Reference

// All annotation options
interface Annotations {
  bold: boolean;
  italic: boolean;
  strikethrough: boolean;
  underline: boolean;
  code: boolean;
  color: 'default' | 'gray' | 'brown' | 'orange' | 'yellow' |
         'green' | 'blue' | 'purple' | 'pink' | 'red' |
         'gray_background' | 'brown_background' | 'orange_background' |
         'yellow_background' | 'green_background' | 'blue_background' |
         'purple_background' | 'pink_background' | 'red_background';
}

// Rich text types: text, mention, equation
const richTextExamples = [
  // Plain text
  { text: { content: 'Hello' } },

  // Text with link
  { text: { content: 'Click here', link: { url: 'https://notion.so' } } },

  // Mention a user
  { mention: { user: { id: 'user-uuid' } } },

  // Mention a page
  { mention: { page: { id: 'page-uuid' } } },

  // Mention a date
  { mention: { date: { start: '2026-04-01' } } },

  // Inline equation (LaTeX)
  { equation: { expression: 'E = mc^2' } },
];

Step 5: Update and Delete Blocks

// Update a block's content
async function updateBlock(blockId: string) {
  await notion.blocks.update({
    block_id: blockId,
    paragraph: {
      rich_text: [{ text: { content: 'Updated content' } }],
    },
  });
}

// Delete (archive) a block
async function deleteBlock(blockId: string) {
  await notion.blocks.delete({ block_id: blockId });
}

Step 6: Work with Comments

// Add a comment to a page
async function addComment(pageId: string, text: string) {
  await notion.comments.create({
    parent: { page_id: pageId },
    rich_text: [{ text: { content: text } }],
  });
}

// Add a comment to a specific block (discussion thread)
async function addBlockComment(discussionId: string, text: string) {
  await notion.comments.create({
    discussion_id: discussionId,
    rich_text: [{ text: { content: text } }],
  });
}

// List comments on a block or page
async function listComments(blockId: string) {
  const response = await notion.comments.list({ block_id: blockId });
  for (const comment of response.results) {
    const text = comment.rich_text.map(t => t.plain_text).join('');
    console.log(`${comment.created_by.id}: ${text}`);
  }
}

Output

  • Page content blocks retrieved (flat or recursive tree)
  • Rich content appended with formatting, lists, code, callouts
  • Blocks updated and deleted
  • Comments created and listed

Error Handling

| Error | Cause | Solution | |-------|-------|----------| | validation_error on append | Invalid block type structure | Check block type object shape | | object_not_found | Block deleted or page not shared | Verify block ID and permissions | | rate_limited (429) | Rapid block operations | Add delays between batch operations | | Empty rich_text array | Block has no text content | Check block type before accessing |

Examples

Build a Report Page

async function buildReport(pageId: string, data: { title: string; items: string[] }) {
  const blocks: any[] = [
    { heading_1: { rich_text: [{ text: { content: data.title } }] } },
    { paragraph: { rich_text: [{ text: { content: `Generated ${new Date().toISOString()}` } }] } },
    { divider: {} },
  ];

  for (const item of data.items) {
    blocks.push({
      bulleted_list_item: { rich_text: [{ text: { content: item } }] },
    });
  }

  await notion.blocks.children.append({ block_id: pageId, children: blocks });
}

Resources

Next Steps

For common errors, see notion-common-errors.