Notion is an all-in-one collaborative workspace combining technical documentation wikis, relational databases, project roadmaps, and API integrations.
When to Use
- Engineering Knowledge Bases: Creating team documentation, API design RFCs, and incident post-mortems.
- Programmatic Database Sync: Syncing GitHub PRs, Jira tickets, and deployment logs into Notion via Official SDK.
- Task & Sprint Tracking: Organizing engineering backlogs, milestone roadmaps, and sprint boards with linked relations.
- Automated Report Generation: Publishing automated daily metrics summaries into Notion pages via Scheduled Lambdas/Workflows.
Quick Start
1. Official Notion SDK Query
import { Client } from "@notionhq/client";
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const response = await notion.databases.query({ database_id: process.env.NOTION_DATABASE_ID, filter: { property: "Status", status: { equals: "In Progress" }, }, sorts: [{ property: "Due Date", direction: "ascending" }], }); console.log( response.results.map((page) => page.properties.Name.title[0]?.plain_text), );
### 2. Install Notion SDK
```bash
npm install @notionhq/client
Core Concepts
Querying Notion Databases via JavaScript SDK
Querying sprint tasks with filters and sorting using @notionhq/client:
import { Client } from "@notionhq/client";
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const DATABASE_ID = process.env.NOTION_DATABASE_ID;
async function getUrgentSprintTasks() {
const response = await notion.databases.query({
database_id: DATABASE_ID,
filter: {
and: [
{
property: "Status",
status: { equals: "In Progress" },
},
{
property: "Priority",
select: { equals: "High" },
},
],
},
sorts: [
{
property: "Due Date",
direction: "ascending",
},
],
});
return response.results.map((page) => ({
id: page.id,
title: page.properties.Name.title[0]?.plain_text,
assignee: page.properties.Assignee.people[0]?.name,
}));
}
Programmatic Page Creation & Block Insertion
Creating an automated deployment log page within an engineering database:
async function createDeploymentLog(version, environment, commitHash) {
const newPage = await notion.pages.create({
parent: { database_id: DATABASE_ID },
properties: {
Name: {
title: [{ text: { content: `Release ${version} [${environment}]` } }],
},
Environment: {
select: { name: environment },
},
Timestamp: {
date: { start: new Date().toISOString() },
},
},
children: [
{
object: "block",
type: "heading_2",
heading_2: {
rich_text: [
{ type: "text", text: { content: "Deployment Details" } },
],
},
},
{
object: "block",
type: "code",
code: {
language: "json",
rich_text: [
{
type: "text",
text: {
content: JSON.stringify(
{ commit: commitHash, status: "SUCCESS" },
null,
2,
),
},
},
],
},
},
],
});
return newPage.id;
}
Formula 2.0 Calculations
Using Notion Formulas 2.0 for business logic and progress tracking:
// Sprint Progress Bar Formula (2.0)
let(
total, prop("Total Tasks"),
done, prop("Completed Tasks"),
percent, if(total > 0, round(done / total * 100), 0),
percent + "% " + substring("■■■■■■■■■■", 0, round(percent / 10)) + substring("□□□□□□□□□□", 0, 10 - round(percent / 10))
)
Common Patterns
Advanced Formula 2.0 (Progress Bar & Date Math)
Problem: Display dynamic progress bar and overdue indicator without external rollup plugins.
Solution: Use Formulas 2.0 functions let(), repeat(), and dateBetween().
/* Formula for Progress Percentage & Bar */
let(
pct, round(prop("Tasks Completed") / max(prop("Total Tasks"), 1) * 100),
let(
filled, round(pct / 10),
repeat("█", filled) + repeat("░", 10 - filled) + " " + pct + "%"
)
)
Append Blocks via Notion API
Problem: Programmatically inject markdown or structured notes into a Notion page.
Solution: Call notion.blocks.children.append().
await notion.blocks.children.append({
block_id: pageId,
children: [
{
object: "block",
type: "heading_2",
heading_2: {
rich_text: [
{ type: "text", text: { content: "Automated Deploy Summary" } },
],
},
},
{
object: "block",
type: "bulleted_list_item",
bulleted_list_item: {
rich_text: [
{ type: "text", text: { content: "Build hash: 8f3d1e (Success)" } },
],
},
},
],
});
Best Practices
Do:
- Store integration tokens securely in environment variables and grant integrations access only to specific parent pages.
- Utilize Database Rollups and 2-way Relations to link Sprints, Epics, and individual Engineering Tasks.
- Handle API rate limits (average 3 requests per second) with exponential backoff and jitter.
- Paginate database query results using
start_cursorandhas_morefor datasets larger than 100 records.
Don't:
- Store sensitive API secrets, database passwords, or private SSH keys in Notion pages.
- Create deep, unorganized page hierarchies; rely on searchable relational databases with views.
- Perform unbounded queries without filters on databases containing thousands of historical rows.
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
object_not_found (404) |
Integration does not have access to the target page or database | In Notion UI, open page menu (...), go to Connections, and invite/add your Integration. |
rate_limited (429) |
Exceeded 3 requests per second limit | Implement exponential backoff retry logic in API client wrappers. |
| Formula 2.0 syntax error | Type mismatch (e.g. attempting arithmetic on string rollups) | Wrap property values with toNumber() or use .map() array syntax for rollup lists. |