Type-Safe API Calls
All client methods are fully typed with request parameters and response types:import { Client } from '@notionhq/client'
import type { PageObjectResponse, GetPageResponse } from '@notionhq/client'
const notion = new Client({ auth: process.env.NOTION_API_KEY })
// TypeScript knows the return type
const response: GetPageResponse = await notion.pages.retrieve({
page_id: 'page-id',
})
// response is PageObjectResponse | PartialPageObjectResponse
if ('url' in response) {
// TypeScript narrows to PageObjectResponse
const url: string = response.url
}
Importing Types
All types are exported from the main package:import type {
// Response types
PageObjectResponse,
BlockObjectResponse,
DatabaseObjectResponse,
UserObjectResponse,
CommentObjectResponse,
// Partial types
PartialPageObjectResponse,
PartialBlockObjectResponse,
PartialDatabaseObjectResponse,
// Request parameter types
CreatePageParameters,
UpdatePageParameters,
QueryDataSourceParameters,
// Response types
CreatePageResponse,
ListBlockChildrenResponse,
SearchResponse,
// Rich text types
RichTextItemResponse,
TextRichTextItemResponse,
MentionRichTextItemResponse,
// Error types
NotionClientError,
APIErrorCode,
ClientErrorCode,
} from '@notionhq/client'
Request Parameter Types
Each API method has a corresponding parameter type:import type { CreatePageParameters } from '@notionhq/client'
const params: CreatePageParameters = {
parent: { database_id: 'database-id' },
properties: {
Name: {
title: [{ text: { content: 'New Page' } }],
},
},
}
await notion.pages.create(params)
Common Parameter Types
import type {
GetBlockParameters,
UpdateBlockParameters,
AppendBlockChildrenParameters,
CreatePageParameters,
UpdatePageParameters,
GetPagePropertyParameters,
QueryDataSourceParameters,
SearchParameters,
ListUsersParameters,
CreateCommentParameters,
} from '@notionhq/client'
Response Types
Each API method returns a typed response:import type {
GetPageResponse,
CreatePageResponse,
UpdatePageResponse,
ListBlockChildrenResponse,
QueryDataSourceResponse,
SearchResponse,
} from '@notionhq/client'
const page: GetPageResponse = await notion.pages.retrieve({
page_id: 'page-id',
})
const blocks: ListBlockChildrenResponse = await notion.blocks.children.list({
block_id: 'block-id',
})
Type Guards
Use type guards to narrow union types:Object Type Guards
import {
isFullPage,
isFullBlock,
isFullDataSource,
isFullDatabase,
isFullUser,
isFullComment,
} from '@notionhq/client'
import type { GetPageResponse } from '@notionhq/client'
const response: GetPageResponse = await notion.pages.retrieve({
page_id: 'page-id',
})
// response is PageObjectResponse | PartialPageObjectResponse
if (isFullPage(response)) {
// response is now PageObjectResponse
console.log(response.url)
console.log(response.created_time)
console.log(response.properties)
}
Full Page or Data Source
import { isFullPageOrDataSource } from '@notionhq/client'
import type { SearchResponse } from '@notionhq/client'
const searchResults: SearchResponse = await notion.search({
query: 'meeting notes',
})
for (const result of searchResults.results) {
if (isFullPageOrDataSource(result)) {
// result is PageObjectResponse | DataSourceObjectResponse
console.log(result.id)
if ('url' in result) {
// result is PageObjectResponse
console.log('Page URL:', result.url)
}
}
}
Rich Text Type Guards
import type { RichTextItemResponse } from '@notionhq/client'
function extractText(richText: RichTextItemResponse[]): string {
return richText
.map(item => {
if (item.type === 'text') {
// TypeScript narrows to TextRichTextItemResponse
return item.text.content
} else if (item.type === 'mention') {
// TypeScript narrows to MentionRichTextItemResponse
return item.mention.type
} else if (item.type === 'equation') {
// TypeScript narrows to EquationRichTextItemResponse
return item.equation.expression
}
return ''
})
.join('')
}
Error Type Guards
import {
isNotionClientError,
isHTTPResponseError,
APIResponseError,
RequestTimeoutError,
} from '@notionhq/client'
try {
await notion.pages.retrieve({ page_id: 'page-id' })
} catch (error: unknown) {
if (isNotionClientError(error)) {
// error is NotionClientError
console.log('Error code:', error.code)
}
if (isHTTPResponseError(error)) {
// error is APIResponseError | UnknownHTTPResponseError
console.log('HTTP status:', error.status)
console.log('Response body:', error.body)
}
if (APIResponseError.isAPIResponseError(error)) {
// error is APIResponseError
console.log('API error:', error.code)
console.log('Request ID:', error.request_id)
}
if (RequestTimeoutError.isRequestTimeoutError(error)) {
// error is RequestTimeoutError
console.log('Request timed out')
}
}
Block Types
Different block types have distinct structures:import type {
BlockObjectResponse,
ParagraphBlockObjectResponse,
Heading1BlockObjectResponse,
CodeBlockObjectResponse,
ImageBlockObjectResponse,
ToDoBlockObjectResponse,
} from '@notionhq/client'
const block: BlockObjectResponse = await notion.blocks.retrieve({
block_id: 'block-id',
})
switch (block.type) {
case 'paragraph':
// block.paragraph is defined
console.log(block.paragraph.rich_text)
break
case 'heading_1':
// block.heading_1 is defined
console.log(block.heading_1.rich_text)
break
case 'code':
// block.code is defined
console.log(block.code.language)
console.log(block.code.rich_text)
break
case 'to_do':
// block.to_do is defined
console.log(block.to_do.checked)
console.log(block.to_do.rich_text)
break
}
Property Types
Database properties have different value types:import type {
PageObjectResponse,
TitlePropertyItemObjectResponse,
RichTextPropertyItemObjectResponse,
NumberPropertyItemObjectResponse,
SelectPropertyItemObjectResponse,
MultiSelectPropertyItemObjectResponse,
DatePropertyItemObjectResponse,
CheckboxPropertyItemObjectResponse,
} from '@notionhq/client'
const page = await notion.pages.retrieve({ page_id: 'page-id' }) as PageObjectResponse
for (const [propertyName, property] of Object.entries(page.properties)) {
switch (property.type) {
case 'title':
console.log('Title:', property.title.map(t => t.plain_text).join(''))
break
case 'rich_text':
console.log('Text:', property.rich_text.map(t => t.plain_text).join(''))
break
case 'number':
console.log('Number:', property.number)
break
case 'select':
console.log('Select:', property.select?.name)
break
case 'multi_select':
console.log('Tags:', property.multi_select.map(s => s.name).join(', '))
break
case 'date':
console.log('Date:', property.date?.start)
break
case 'checkbox':
console.log('Checked:', property.checkbox)
break
}
}
Generic Type Utilities
Extract types from SDK types:import type { BlockObjectResponse } from '@notionhq/client'
// Extract specific block type
type ParagraphBlock = Extract<BlockObjectResponse, { type: 'paragraph' }>
type HeadingBlock = Extract<BlockObjectResponse, { type: 'heading_1' | 'heading_2' | 'heading_3' }>
type CodeBlock = Extract<BlockObjectResponse, { type: 'code' }>
function processParagraph(block: ParagraphBlock) {
// block.paragraph is always defined
const text = block.paragraph.rich_text.map(t => t.plain_text).join('')
return text
}
Helper Type Examples
Create Type-Safe Wrappers
import type { CreatePageParameters, PageObjectResponse } from '@notionhq/client'
interface DatabasePage {
title: string
status: string
tags: string[]
dueDate?: string
}
function createDatabasePage(
databaseId: string,
page: DatabasePage
): CreatePageParameters {
return {
parent: { database_id: databaseId },
properties: {
Name: {
title: [{ text: { content: page.title } }],
},
Status: {
select: { name: page.status },
},
Tags: {
multi_select: page.tags.map(tag => ({ name: tag })),
},
...(page.dueDate && {
'Due Date': {
date: { start: page.dueDate },
},
}),
},
}
}
await notion.pages.create(
createDatabasePage('database-id', {
title: 'New Task',
status: 'In Progress',
tags: ['urgent', 'bug'],
dueDate: '2026-03-10',
})
)
Extract Property Values
import type { PageObjectResponse } from '@notionhq/client'
function getPageTitle(page: PageObjectResponse): string {
for (const property of Object.values(page.properties)) {
if (property.type === 'title') {
return property.title.map(t => t.plain_text).join('')
}
}
return 'Untitled'
}
function getPropertyValue(
page: PageObjectResponse,
propertyName: string
): unknown {
const property = page.properties[propertyName]
if (!property) return undefined
switch (property.type) {
case 'title':
return property.title.map(t => t.plain_text).join('')
case 'rich_text':
return property.rich_text.map(t => t.plain_text).join('')
case 'number':
return property.number
case 'select':
return property.select?.name
case 'multi_select':
return property.multi_select.map(s => s.name)
case 'date':
return property.date
case 'checkbox':
return property.checkbox
default:
return undefined
}
}
Type-Safe Configuration
import { Client, LogLevel } from '@notionhq/client'
import type { ClientOptions, RetryOptions } from '@notionhq/client'
const retryConfig: RetryOptions = {
maxRetries: 3,
initialRetryDelayMs: 1000,
maxRetryDelayMs: 30000,
}
const clientOptions: ClientOptions = {
auth: process.env.NOTION_API_KEY,
logLevel: LogLevel.INFO,
timeoutMs: 60000,
retry: retryConfig,
}
const notion = new Client(clientOptions)
Working with Unknown Types
When dealing with dynamic data, use type assertions carefully:import type { PageObjectResponse } from '@notionhq/client'
import { isFullPage } from '@notionhq/client'
const response = await notion.pages.retrieve({ page_id: 'page-id' })
// Option 1: Type guard
if (isFullPage(response)) {
const page: PageObjectResponse = response
console.log(page.url)
}
// Option 2: Type assertion (use sparingly)
const page = response as PageObjectResponse
if ('url' in page) {
console.log(page.url)
}
Use type assertions sparingly. Prefer type guards like
isFullPage() to safely narrow types.Best Practices
- Import types separately - Use
import type { ... }for type-only imports - Use type guards - Prefer
isFullPage()over manual type checks - Leverage discriminated unions - Use
typefields to narrow block/property types - Avoid
any- The SDK provides complete types; use them - Type function parameters - Explicitly type parameters that accept SDK types
import type { BlockObjectResponse, PageObjectResponse } from '@notionhq/client'
import { isFullBlock, isFullPage } from '@notionhq/client'
// Good: Explicitly typed and type-guarded
async function getPageBlocks(
notion: Client,
pageId: string
): Promise<BlockObjectResponse[]> {
const response = await notion.blocks.children.list({ block_id: pageId })
return response.results.filter(isFullBlock)
}
// Good: Type guard before accessing properties
function extractPageTitle(page: GetPageResponse): string {
if (isFullPage(page)) {
return getPageTitle(page)
}
return 'Unknown'
}
// Avoid: Implicit any
// function processPage(page) { ... }
// Avoid: Type assertion without guard
// const page = response as PageObjectResponse