Skip to content

SOP 006: Manage Blocks

Fresh Last Updated: January 2025
FieldValue
SOP IDSOP-006
Version1.0
StatusActive
DifficultyIntermediate

Purpose

Add, retrieve, update, and delete content blocks within Notion pages. Blocks are the building units of page content.

Prerequisites

  • [ ] Completed authentication setup (SOP 002)
  • [ ] Page ID with existing content or empty page
  • [ ] Update content capability enabled

Block Operations Flow

Step-by-Step Instructions

Step 1: Retrieve Page Blocks

javascript
const { Client } = require('@notionhq/client');
require('dotenv').config();

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

async function getPageBlocks(pageId) {
  const blocks = [];
  let cursor = 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;
}

// Usage
const blocks = await getPageBlocks('page-id-here');
console.log(`Found ${blocks.length} blocks`);

Step 2: Get Nested Blocks Recursively

javascript
async function getAllBlocksRecursive(blockId, depth = 0) {
  const blocks = [];
  let cursor = undefined;

  do {
    const response = await notion.blocks.children.list({
      block_id: blockId,
      start_cursor: cursor
    });

    for (const block of response.results) {
      blocks.push({ ...block, depth });

      // Recursively get children if block has them
      if (block.has_children) {
        const children = await getAllBlocksRecursive(block.id, depth + 1);
        blocks.push(...children);
      }
    }

    cursor = response.has_more ? response.next_cursor : undefined;
  } while (cursor);

  return blocks;
}

Step 3: Block Types Reference

Step 4: Append Blocks to Page

javascript
async function appendBlocks(pageId) {
  const response = await notion.blocks.children.append({
    block_id: pageId,
    children: [
      // Heading
      {
        object: 'block',
        type: 'heading_2',
        heading_2: {
          rich_text: [{ text: { content: 'Section Title' } }]
        }
      },
      // Paragraph with formatting
      {
        object: 'block',
        type: 'paragraph',
        paragraph: {
          rich_text: [
            { text: { content: 'Regular text, ' } },
            {
              text: { content: 'bold' },
              annotations: { bold: true }
            },
            { text: { content: ', ' } },
            {
              text: { content: 'italic' },
              annotations: { italic: true }
            },
            { text: { content: ', and ' } },
            {
              text: { content: 'code' },
              annotations: { code: true }
            },
            { text: { content: '.' } }
          ]
        }
      },
      // Bulleted list
      {
        object: 'block',
        type: 'bulleted_list_item',
        bulleted_list_item: {
          rich_text: [{ text: { content: 'First bullet' } }]
        }
      },
      {
        object: 'block',
        type: 'bulleted_list_item',
        bulleted_list_item: {
          rich_text: [{ text: { content: 'Second bullet' } }]
        }
      }
    ]
  });

  console.log(`Added ${response.results.length} blocks`);
  return response;
}

Step 5: All Block Type Examples

javascript
// Paragraph
{
  paragraph: {
    rich_text: [{ text: { content: 'Paragraph text' } }],
    color: 'default' // or 'gray', 'brown', 'orange', etc.
  }
}

// Headings
{ heading_1: { rich_text: [{ text: { content: 'H1' } }] } }
{ heading_2: { rich_text: [{ text: { content: 'H2' } }] } }
{ heading_3: { rich_text: [{ text: { content: 'H3' } }] } }

// Quote
{
  quote: {
    rich_text: [{ text: { content: 'Quoted text' } }]
  }
}

// Callout with emoji
{
  callout: {
    rich_text: [{ text: { content: 'Important note' } }],
    icon: { emoji: '⚠️' },
    color: 'yellow_background'
  }
}
javascript
// Bulleted list
{
  bulleted_list_item: {
    rich_text: [{ text: { content: 'Bullet item' } }]
  }
}

// Numbered list
{
  numbered_list_item: {
    rich_text: [{ text: { content: 'Numbered item' } }]
  }
}

// To-do
{
  to_do: {
    rich_text: [{ text: { content: 'Task to do' } }],
    checked: false
  }
}

// Toggle
{
  toggle: {
    rich_text: [{ text: { content: 'Click to expand' } }],
    children: [
      {
        paragraph: {
          rich_text: [{ text: { content: 'Hidden content' } }]
        }
      }
    ]
  }
}
javascript
{
  code: {
    rich_text: [{
      text: {
        content: 'function hello() {\n  console.log("Hello!");\n}'
      }
    }],
    language: 'javascript',
    caption: [{ text: { content: 'Example function' } }]
  }
}

// Supported languages: javascript, python, java, c, cpp,
// csharp, go, rust, ruby, php, swift, kotlin, typescript,
// html, css, json, yaml, markdown, sql, bash, and more
javascript
// External image
{
  image: {
    type: 'external',
    external: {
      url: 'https://example.com/image.png'
    },
    caption: [{ text: { content: 'Image caption' } }]
  }
}

// Bookmark
{
  bookmark: {
    url: 'https://example.com',
    caption: [{ text: { content: 'Link description' } }]
  }
}

// Embed (supported sites)
{
  embed: {
    url: 'https://twitter.com/user/status/123'
  }
}

// Video
{
  video: {
    type: 'external',
    external: {
      url: 'https://www.youtube.com/watch?v=xyz'
    }
  }
}
javascript
// Divider
{ divider: {} }

// Table of contents
{ table_of_contents: { color: 'default' } }

// Breadcrumb
{ breadcrumb: {} }

// Column list (must contain column blocks)
{
  column_list: {
    children: [
      {
        column: {
          children: [
            { paragraph: { rich_text: [{ text: { content: 'Col 1' } }] } }
          ]
        }
      },
      {
        column: {
          children: [
            { paragraph: { rich_text: [{ text: { content: 'Col 2' } }] } }
          ]
        }
      }
    ]
  }
}
javascript
{
  table: {
    table_width: 3,
    has_column_header: true,
    has_row_header: false,
    children: [
      {
        table_row: {
          cells: [
            [{ text: { content: 'Header 1' } }],
            [{ text: { content: 'Header 2' } }],
            [{ text: { content: 'Header 3' } }]
          ]
        }
      },
      {
        table_row: {
          cells: [
            [{ text: { content: 'Row 1, Col 1' } }],
            [{ text: { content: 'Row 1, Col 2' } }],
            [{ text: { content: 'Row 1, Col 3' } }]
          ]
        }
      }
    ]
  }
}

Step 6: Update a Block

javascript
async function updateBlock(blockId) {
  // Update paragraph content
  const response = await notion.blocks.update({
    block_id: blockId,
    paragraph: {
      rich_text: [
        {
          text: { content: 'Updated paragraph content' }
        }
      ]
    }
  });

  return response;
}

// Update to-do checkbox
async function toggleTodo(blockId, checked) {
  return notion.blocks.update({
    block_id: blockId,
    to_do: {
      checked: checked
    }
  });
}

// Archive (soft delete) a block
async function archiveBlock(blockId) {
  return notion.blocks.update({
    block_id: blockId,
    archived: true
  });
}

Step 7: Delete a Block

javascript
async function deleteBlock(blockId) {
  const response = await notion.blocks.delete({
    block_id: blockId
  });

  console.log('Block deleted:', blockId);
  return response;
}

// Delete multiple blocks
async function deleteBlocks(blockIds) {
  const results = [];

  for (const id of blockIds) {
    try {
      await notion.blocks.delete({ block_id: id });
      results.push({ id, success: true });
    } catch (error) {
      results.push({ id, success: false, error: error.message });
    }

    // Rate limit protection
    await new Promise(resolve => setTimeout(resolve, 350));
  }

  return results;
}

Step 8: Insert Block at Specific Position

javascript
async function insertAfterBlock(parentId, afterBlockId, newBlocks) {
  const response = await notion.blocks.children.append({
    block_id: parentId,
    after: afterBlockId,  // Insert after this block
    children: newBlocks
  });

  return response;
}

// Usage
await insertAfterBlock(pageId, existingBlockId, [
  {
    paragraph: {
      rich_text: [{ text: { content: 'Inserted after specific block' } }]
    }
  }
]);

Complete Example: Build Document Structure

javascript
async function buildDocumentStructure(pageId) {
  // Clear existing content (optional)
  const existingBlocks = await getPageBlocks(pageId);
  for (const block of existingBlocks) {
    await notion.blocks.delete({ block_id: block.id });
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  // Build new structure
  const response = await notion.blocks.children.append({
    block_id: pageId,
    children: [
      // Header section
      {
        callout: {
          rich_text: [{ text: { content: 'Project Overview Document' } }],
          icon: { emoji: '📄' },
          color: 'blue_background'
        }
      },
      { divider: {} },

      // Table of contents
      { table_of_contents: { color: 'gray' } },

      // Introduction
      {
        heading_1: {
          rich_text: [{ text: { content: 'Introduction' } }]
        }
      },
      {
        paragraph: {
          rich_text: [{
            text: { content: 'This document provides an overview of the project scope, timeline, and deliverables.' }
          }]
        }
      },

      // Goals section with toggle
      {
        heading_1: {
          rich_text: [{ text: { content: 'Project Goals' } }]
        }
      },
      {
        toggle: {
          rich_text: [{ text: { content: 'View all goals' } }],
          children: [
            {
              numbered_list_item: {
                rich_text: [{ text: { content: 'Improve user experience' } }]
              }
            },
            {
              numbered_list_item: {
                rich_text: [{ text: { content: 'Increase performance by 50%' } }]
              }
            },
            {
              numbered_list_item: {
                rich_text: [{ text: { content: 'Add mobile support' } }]
              }
            }
          ]
        }
      },

      // Tasks section
      {
        heading_1: {
          rich_text: [{ text: { content: 'Tasks' } }]
        }
      },
      {
        to_do: {
          rich_text: [{ text: { content: 'Complete requirements gathering' } }],
          checked: true
        }
      },
      {
        to_do: {
          rich_text: [{ text: { content: 'Design system architecture' } }],
          checked: true
        }
      },
      {
        to_do: {
          rich_text: [{ text: { content: 'Implement core features' } }],
          checked: false
        }
      },
      {
        to_do: {
          rich_text: [{ text: { content: 'Write tests' } }],
          checked: false
        }
      },

      // Code example
      {
        heading_1: {
          rich_text: [{ text: { content: 'Code Reference' } }]
        }
      },
      {
        code: {
          rich_text: [{
            text: {
              content: `// Initialize the application
const app = new Application({
  debug: process.env.NODE_ENV !== 'production'
});

app.start();`
            }
          }],
          language: 'javascript',
          caption: [{ text: { content: 'Startup code' } }]
        }
      }
    ]
  });

  console.log(`✅ Created ${response.results.length} blocks`);
  return response;
}

Extract Text from Blocks

javascript
function extractText(blocks) {
  const texts = [];

  for (const block of blocks) {
    const type = block.type;
    const content = block[type];

    if (content?.rich_text) {
      const text = content.rich_text
        .map(rt => rt.plain_text)
        .join('');

      if (text) {
        texts.push({
          type,
          text,
          id: block.id
        });
      }
    }
  }

  return texts;
}

// Usage
const blocks = await getPageBlocks(pageId);
const textContent = extractText(blocks);
console.log(textContent);

Verification Checklist

  • [ ] Can retrieve all blocks from page
  • [ ] Blocks append in correct order
  • [ ] Block updates reflect changes
  • [ ] Deleted blocks no longer appear
  • [ ] Nested blocks retrieved recursively

Troubleshooting

IssueCauseSolution
Empty resultsNo children or wrong IDVerify page has content
400 Invalid blockWrong block structureCheck block type format
Nesting limitToo deepMax 2 levels on create
Block not foundWrong ID or deletedVerify block exists

Common Mistakes

  1. Wrong block structure - Each block type has specific required fields
  2. Missing rich_text array - Text content must be wrapped in rich_text array
  3. Nesting too deep - Can only nest 2 levels when appending
  4. Forgetting object/type - Include object: 'block' and type when appending

See Also

Built with VitePress | Powered by Claude AI