Agent skill
notion-integration
Notion integration patterns for knowledge management. Covers API, databases, pages, and blocks. Use this skill when implementing Notion features or syncing sessions with databases.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/notion-integration
SKILL.md
Notion Integration Skill
Integration patterns for syncing development sessions with Notion databases and pages.
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ NOTION INTEGRATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Claude │────►│ REST API │────►│ Notion │ │
│ │ Session │◄────│ Client │◄────│ API │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Session │ │ Databases │ │
│ │ Files │ │ & Pages │ │
│ └─────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
When to Use This Skill
- Setting up Notion integration for the first time
- Syncing development sessions with Notion databases
- Creating database entries from Claude sessions
- Updating page properties and content
- Managing documentation in Notion
Configuration
.claude/config/workspace.json
{
"taskManager": {
"enabled": true,
"provider": "notion",
"syncWithSession": true,
"autoUpdateStatus": true,
"config": {
"integrationToken": "${NOTION_TOKEN}",
"tasksDatabaseId": "your-database-id",
"defaultWorkspace": "your-workspace-id"
}
}
}
Environment Variables
# .env.local
NOTION_TOKEN=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authentication
Integration Token
// Bearer token authentication
const headers = {
'Authorization': `Bearer ${process.env.NOTION_TOKEN}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28' // Required API version
};
// Base URL
const BASE_URL = 'https://api.notion.com/v1';
Integration Setup
- Go to https://www.notion.so/my-integrations
- Create new integration
- Copy the "Internal Integration Token"
- Share databases/pages with the integration
API Patterns
Common Endpoints
| Endpoint | Method | Description |
|---|---|---|
/users/me |
GET | Get bot user |
/users |
GET | List users |
/databases/{id} |
GET | Get database |
/databases/{id}/query |
POST | Query database |
/pages |
POST | Create page |
/pages/{id} |
GET/PATCH | Page operations |
/pages/{id}/properties/{id} |
GET | Get property value |
/blocks/{id} |
GET/PATCH/DELETE | Block operations |
/blocks/{id}/children |
GET/PATCH | Block children |
/search |
POST | Search pages/databases |
Database Entry (Page) Structure
interface NotionPage {
id: string;
object: 'page';
parent: {
type: 'database_id';
database_id: string;
};
properties: {
// Title property (required)
Name: {
title: Array<{
type: 'text';
text: { content: string };
}>;
};
// Select property
Status: {
select: {
name: string;
color: string;
} | null;
};
// Multi-select property
Tags: {
multi_select: Array<{
name: string;
color: string;
}>;
};
// Date property
'Due Date': {
date: {
start: string;
end: string | null;
} | null;
};
// Person property
Assignee: {
people: Array<{
id: string;
name: string;
}>;
};
// Checkbox property
Completed: {
checkbox: boolean;
};
// URL property
URL: {
url: string | null;
};
// Rich text property
Description: {
rich_text: Array<{
type: 'text';
text: { content: string };
}>;
};
};
url: string;
}
Database Operations
Query Database
// Query with filter and sort
const response = await fetch(`${BASE_URL}/databases/${databaseId}/query`, {
method: 'POST',
headers,
body: JSON.stringify({
filter: {
and: [
{
property: 'Status',
select: { equals: 'In Progress' }
},
{
property: 'Assignee',
people: { contains: userId }
}
]
},
sorts: [
{ property: 'Due Date', direction: 'ascending' }
],
page_size: 100
})
});
const { results, has_more, next_cursor } = await response.json();
Create Database Entry
// Create new page in database
const page = await fetch(`${BASE_URL}/pages`, {
method: 'POST',
headers,
body: JSON.stringify({
parent: { database_id: databaseId },
properties: {
Name: {
title: [{ text: { content: 'New Feature: User Auth' } }]
},
Status: {
select: { name: 'In Progress' }
},
Tags: {
multi_select: [
{ name: 'feature' },
{ name: 'backend' }
]
},
'Due Date': {
date: { start: '2024-01-15' }
},
Description: {
rich_text: [{ text: { content: 'Implement OAuth2 support' } }]
}
}
})
});
Update Properties
// Update page properties
await fetch(`${BASE_URL}/pages/${pageId}`, {
method: 'PATCH',
headers,
body: JSON.stringify({
properties: {
Status: {
select: { name: 'Done' }
},
Completed: {
checkbox: true
}
}
})
});
Block Operations
Notion pages consist of blocks. Common block types:
Block Types
| Type | Description |
|---|---|
paragraph |
Text paragraph |
heading_1/2/3 |
Headings |
bulleted_list_item |
Bullet point |
numbered_list_item |
Numbered item |
to_do |
Checkbox item |
toggle |
Collapsible block |
code |
Code block |
callout |
Callout box |
quote |
Quote block |
divider |
Horizontal line |
table |
Table |
table_row |
Table row |
Adding Content to Page
// Append blocks to page
await fetch(`${BASE_URL}/blocks/${pageId}/children`, {
method: 'PATCH',
headers,
body: JSON.stringify({
children: [
{
object: 'block',
type: 'heading_2',
heading_2: {
rich_text: [{ type: 'text', text: { content: 'Session Progress' } }]
}
},
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: 'Phase: Backend Development' } }]
}
},
{
object: 'block',
type: 'to_do',
to_do: {
rich_text: [{ type: 'text', text: { content: 'API endpoints' } }],
checked: true
}
},
{
object: 'block',
type: 'to_do',
to_do: {
rich_text: [{ type: 'text', text: { content: 'Frontend components' } }],
checked: false
}
},
{
object: 'block',
type: 'code',
code: {
rich_text: [{ type: 'text', text: { content: 'npm run test' } }],
language: 'bash'
}
}
]
})
});
Session Sync Patterns
Linking Session to Database Entry
# In requirements.md
## Notion Task
- **Page ID:** abc123-def456-...
- **URL:** https://notion.so/workspace/Task-Name-abc123def456
- **Status:** In Progress
Status Mapping
| Session Event | Notion Status |
|---|---|
| Session created | "In Progress" |
| Blocked | "Blocked" |
| Session closed (success) | "Done" |
| Session closed (partial) | "In Review" |
Progress Updates
// Update page with progress
const progressBlocks = [
{
object: 'block',
type: 'divider',
divider: {}
},
{
object: 'block',
type: 'heading_3',
heading_3: {
rich_text: [{
type: 'text',
text: { content: `Update: ${new Date().toISOString().split('T')[0]}` }
}]
}
},
{
object: 'block',
type: 'bulleted_list_item',
bulleted_list_item: {
rich_text: [{ type: 'text', text: { content: 'API endpoints completed' } }]
}
},
{
object: 'block',
type: 'bulleted_list_item',
bulleted_list_item: {
rich_text: [{ type: 'text', text: { content: 'Tests written and passing' } }]
}
}
];
await fetch(`${BASE_URL}/blocks/${pageId}/children`, {
method: 'PATCH',
headers,
body: JSON.stringify({ children: progressBlocks })
});
Rich Text Formatting
Text with Formatting
const richText = [
{ type: 'text', text: { content: 'Normal text, ' } },
{
type: 'text',
text: { content: 'bold text' },
annotations: { bold: true }
},
{ type: 'text', text: { content: ', ' } },
{
type: 'text',
text: { content: 'italic' },
annotations: { italic: true }
},
{ type: 'text', text: { content: ', ' } },
{
type: 'text',
text: { content: 'code' },
annotations: { code: true }
},
{ type: 'text', text: { content: ', and ' } },
{
type: 'text',
text: { content: 'link', link: { url: 'https://example.com' } }
}
];
Available Annotations
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' | /* etc */;
}
Rate Limiting
Limits
| Type | Limit |
|---|---|
| Requests | 3 requests/second average |
| Burst | Short bursts allowed |
Handling Rate Limits
// Notion returns 429 with Retry-After header
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || '1';
await sleep(parseInt(retryAfter) * 1000);
// Retry request
}
// Implement exponential backoff
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status !== 429) return response;
const delay = Math.pow(2, i) * 1000;
await sleep(delay);
}
throw new Error('Max retries exceeded');
}
Error Handling
Common Error Codes
| Code | Description | Resolution |
|---|---|---|
| 400 | Invalid request | Check body format |
| 401 | Unauthorized | Check token |
| 403 | No access | Share page with integration |
| 404 | Not found | Verify page/database ID |
| 409 | Conflict | Transaction conflict, retry |
| 429 | Rate limited | Wait and retry |
Error Response Format
{
"object": "error",
"status": 400,
"code": "validation_error",
"message": "body failed validation..."
}
Testing Patterns
Mock API Responses
// __mocks__/notion.ts
export const mockPage = {
id: "abc123-def456",
object: "page",
properties: {
Name: { title: [{ text: { content: "Test Task" } }] },
Status: { select: { name: "In Progress" } }
}
};
export const mockQueryDatabase = jest.fn().mockResolvedValue({
results: [mockPage],
has_more: false
});
Integration Tests
describe('Notion Integration', () => {
it('should query database', async () => {
const results = await notion.queryDatabase(databaseId, {
filter: { property: 'Status', select: { equals: 'In Progress' } }
});
expect(results.length).toBeGreaterThan(0);
});
it('should create page', async () => {
const page = await notion.createPage({
parent: { database_id: databaseId },
properties: {
Name: { title: [{ text: { content: 'Test' } }] }
}
});
expect(page.id).toBeDefined();
});
});
Anti-Patterns
DON'T: Forget Notion-Version Header
// BAD - Missing version header
fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
// GOOD - Include version
fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Notion-Version': '2022-06-28'
}
});
DON'T: Assume Integration Has Access
// BAD - Assume access exists
const page = await notion.getPage(pageId);
// GOOD - Handle access errors
try {
const page = await notion.getPage(pageId);
} catch (error) {
if (error.code === 'object_not_found') {
console.error('Page not shared with integration');
// Guide user to share the page
}
}
DON'T: Send Empty Arrays
// BAD - Empty arrays can cause errors
properties: {
Tags: { multi_select: [] } // Remove tags
}
// GOOD - Omit property or use null where supported
properties: {
// Don't include Tags to keep existing values
}
Checklist
Before using Notion integration:
- Integration created at notion.so/my-integrations
- Token stored in environment variables
- Database shared with integration
- Notion-Version header set (2022-06-28)
- Property names match database schema exactly
- Rate limiting handled
- Error handling for 403/404
Related Skills
session-management- Session lifecyclescheduled-actions- Background task processingservice-layer- API implementation patterns
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?