SOP 006: Manage Blocks
Fresh Last Updated: January 2025| Field | Value |
|---|---|
| SOP ID | SOP-006 |
| Version | 1.0 |
| Status | Active |
| Difficulty | Intermediate |
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 morejavascript
// 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
| Issue | Cause | Solution |
|---|---|---|
| Empty results | No children or wrong ID | Verify page has content |
| 400 Invalid block | Wrong block structure | Check block type format |
| Nesting limit | Too deep | Max 2 levels on create |
| Block not found | Wrong ID or deleted | Verify block exists |
Common Mistakes
- Wrong block structure - Each block type has specific required fields
- Missing rich_text array - Text content must be wrapped in rich_text array
- Nesting too deep - Can only nest 2 levels when appending
- Forgetting object/type - Include
object: 'block'andtypewhen appending