Append-only: The Notion API supports appending blocks at the end of a page but does not support inserting blocks at a specific position or replacing existing block content. If you need to update content, consider creating a new page or archiving and recreating the page with updated blocks.
When to Use
- Deployment log entries: Append a paragraph block to an incident or deployment page each time a CI/CD pipeline step completes, building a running log of deployment events on the page.
- Weekly metrics append: Each week, append a new section with the latest metrics to a running report page rather than creating a new page per week.
- Action items after meeting summarization: After a FlowAiAgent node extracts action items from meeting notes, append them as to-do blocks back to the meeting notes page.
- Bug tracking annotation: When a bug report is triaged, append a structured summary block to the bug page with severity assessment and assigned team.
- Test results recording: After a test suite runs, append a code block or table block containing test output to a QA tracking page.
Configuration
Authentication
| Field | Required | Description |
AuthenticationMethod | Required | "ApiToken" or "OAuth2". |
ApiToken | Required | Notion Internal Integration Token starting with secret_. |
ClientId | OAuth2 | OAuth2 client ID. |
ClientSecret | OAuth2 | OAuth2 client secret. |
AccessToken | OAuth2 | OAuth2 access token. |
Operation Fields
| Field | Required | Description |
BlockId | Required | UUID of the parent page or block to append content to. Use a page UUID to append to the page body. |
Blocks | Required | Array of Notion block objects to append. Maximum 100 blocks per call. See block format examples below. |
Sample Configuration
{
"resource": "block",
"operation": "appendChildren",
"AuthenticationMethod": "ApiToken",
"ApiToken": "{{ $credentials.notionApiToken }}",
"BlockId": "{{ $json.pageId }}",
"Blocks": [
{
"object": "block",
"type": "heading_2",
"heading_2": {
"rich_text": [{ "type": "text", "text": { "content": "Deployment — {{ $now | date: 'YYYY-MM-DD HH:mm' }} UTC" } }]
}
},
{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{ "type": "text", "text": { "content": "Environment: {{ $json.environment }}\nBuild: {{ $json.buildId }}\nStatus: {{ $json.status }}" } }]
}
},
{
"object": "block",
"type": "divider",
"divider": {}
}
]
}
Block Type Reference
| Block Type | Key | Description |
paragraph | paragraph.rich_text | Standard text paragraph. |
heading_1 | heading_1.rich_text | Large heading. |
heading_2 | heading_2.rich_text | Medium heading. |
heading_3 | heading_3.rich_text | Small heading. |
bulleted_list_item | bulleted_list_item.rich_text | Bulleted list item. |
numbered_list_item | numbered_list_item.rich_text | Numbered list item. |
to_do | to_do.rich_text, to_do.checked | Checkbox to-do item. |
code | code.rich_text, code.language | Code block with syntax highlighting. |
divider | divider: {} | Horizontal rule separator. |
callout | callout.rich_text, callout.icon | Callout box with icon. |
Validation Errors
| Error Code | Cause |
NOTION_VALIDATION_FAILED | BlockId is empty, not a valid UUID, or Blocks array is empty. |
NOTION_INVALID_REQUEST | A block object is malformed — missing required type key or rich_text structure. Verify each block has object: "block", type, and the type-specific content object. |
NOTION_NOT_FOUND | Parent page or block UUID does not exist. |
NOTION_PERMISSION_DENIED | Integration not shared with this page. |
NOTION_RATE_LIMITED | Rate limit exceeded. |
Output
Success Port
| Field | Type | Description |
results | array | Array of the newly created block objects, each with id, type, created_time, and type-specific content. |
Error Port
On failure the error port receives errorCode, message, and httpStatus. A malformed block at any position causes the entire call to fail — none of the blocks in the array are appended on error.
Sample Output
{
"results": [
{
"id": "a3b4c5d6-e7f8-9012-abcd-234567890123",
"type": "heading_2",
"created_time": "2024-06-11T14:32:00.000Z",
"heading_2": {
"rich_text": [{ "plain_text": "Deployment — 2024-06-11 14:32 UTC" }]
}
},
{
"id": "b4c5d6e7-f8a9-0123-bcde-345678901234",
"type": "paragraph",
"created_time": "2024-06-11T14:32:00.000Z",
"paragraph": {
"rich_text": [{ "plain_text": "Environment: production\nBuild: build-4821\nStatus: Success" }]
}
}
]
}
Expression Reference
| Expression | Type | Use |
{{ $output.appendBlocks.results.length }} | number | Confirm number of blocks appended |
{{ $output.appendBlocks.results[0].id }} | string | Get ID of first appended block for nested appends |
{{ $output.appendBlocks.results[0].created_time }} | string | Log append timestamp for audit |
Node Policies & GuardRails
| Policy | Detail |
| Maximum 100 blocks per call | The Notion API enforces a 100-block limit per append call. For large content sets, split into batches of 100 and call block/appendChildren multiple times sequentially. |
| All-or-nothing append | If any block in the Blocks array is malformed, the entire call fails and no blocks are appended. Validate block structure in a Function node before calling this operation. |
| No insert or replace | Blocks are always appended at the end. There is no way to insert blocks at a specific position. Plan page structure so that appended content goes at the bottom, or use page/create with blocks for fully structured pages. |
| Check parent exists first | If the parent page ID comes from a prior workflow step (e.g., a newly created page), verify the create operation succeeded before calling this node. An invalid parent ID causes NOTION_NOT_FOUND. |
| Credentials in Credentials Manager | Always use {{ $credentials.notionApiToken }} — never hard-code the token. |
Workflow Examples
Example 1 — Running Deployment Log
Each time a deployment pipeline fires, append a timestamped entry to a central deployment log page.
// Step 1: WebhookTrigger (CI/CD pipeline complete)
// Step 2: block/appendChildren
{
"BlockId": "{{ $vars.deploymentLogPageId }}",
"Blocks": [
{
"object": "block", "type": "heading_3",
"heading_3": { "rich_text": [{ "text": { "content": "{{ $now | date: 'YYYY-MM-DD HH:mm' }} — {{ $json.service }}" } }] }
},
{
"object": "block", "type": "bulleted_list_item",
"bulleted_list_item": { "rich_text": [{ "text": { "content": "Status: {{ $json.status }}" } }] }
},
{
"object": "block", "type": "bulleted_list_item",
"bulleted_list_item": { "rich_text": [{ "text": { "content": "Build: {{ $json.buildId }}" } }] }
},
{ "object": "block", "type": "divider", "divider": {} }
]
}
Example 2 — Append AI Summary to Meeting Notes
After summarizing a meeting page with FlowAiAgent, append the summary and action items back to the original page.
// Step 1: block/getChildren (read existing meeting notes)
// Step 2: FlowAiAgent (summarize + extract action items)
// Step 3: Function node — build block array from AI output
const blocks = [
{ object: "block", type: "divider", divider: {} },
{ object: "block", type: "heading_2", heading_2: { rich_text: [{ text: { content: "AI Summary" } }] } },
{ object: "block", type: "paragraph", paragraph: { rich_text: [{ text: { content: $json.summary } }] } },
{ object: "block", type: "heading_3", heading_3: { rich_text: [{ text: { content: "Action Items" } }] } },
...$json.actionItems.map(item => ({
object: "block", type: "to_do",
to_do: { rich_text: [{ text: { content: item } }], checked: false }
}))
];
return { blocks };
// Step 4: block/appendChildren with generated blocks
Example 3 — Batch Large Content Append
Append more than 100 blocks by splitting into batches.
// Step 1: Build allBlocks array (200 items)
// Step 2: Split into batches of 100
const batches = [];
for (let i = 0; i < allBlocks.length; i += 100) {
batches.push(allBlocks.slice(i, i + 100));
}
// Step 3: Loop over batches
// Step 4 per batch: block/appendChildren with batch items