Skip to content

Configuration & Features

Configure the Notion client with various options to customize behavior.


Initialize the Notion client with configuration options:

import { Notion } from '@visus-io/notion-sdk-ts';
const notion = new Notion({
auth: process.env.NOTION_TOKEN, // Required
// All other options are optional
});

The auth parameter is required. It must contain your Notion integration token.

  1. Go to https://www.notion.so/my-integrations.
  2. Click “New integration”.
  3. Give the integration a name.
  4. Select the capabilities you need.
  5. Copy the “Internal Integration Token”.
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
});

Security best practice: Always use environment variables for tokens. Do not hardcode tokens in your code.

Terminal window
# .env file
NOTION_TOKEN=secret_your_token_here
// Load from .env
import { config } from 'dotenv';
config();
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
});

The SDK uses Notion API version 2026-03-11. This version is a fixed constant. You cannot override it with client options. All schemas, request bodies, and helpers depend on this version.

The SDK exports the target version as a read-only constant:

import { NOTION_VERSION } from '@visus-io/notion-sdk-ts';
console.log(NOTION_VERSION); // '2026-03-11'
// Useful for logging or conditional logic
console.log(`Using Notion API version: ${NOTION_VERSION}`);

Every outgoing HTTP request carries the header Notion-Version: 2026-03-11 automatically. You do not need to configure this header.

See the Migration Guide to upgrade from an earlier SDK version.


Set how long the client waits for a response before the request times out.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
// Default: 60,000ms (60 seconds)
});
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
timeoutMs: 30_000, // 30 seconds
});
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
timeoutMs: 5_000, // 5 seconds (for fast-fail scenarios)
});
import { NotionRequestTimeoutError } from '@visus-io/notion-sdk-ts';
try {
await notion.pages.retrieve('page-id');
} catch (error) {
if (error instanceof NotionRequestTimeoutError) {
console.error('Request timed out after', error.message);
}
}

The SDK handles rate limiting automatically with retry logic.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
retryOnRateLimit: true, // Default: automatically retry 429 responses
maxRetries: 3, // Default: retry up to 3 times
});

How it works:

  1. The SDK receives a retryable response: 429 Too Many Requests, 529 Service Overload, or a 500 to 599 server error.
  2. The SDK checks the Retry-After header from the Notion API.
  3. If the header is missing or invalid, the SDK reads additional_data.retry_after from the error body instead.
  4. The SDK waits for the duration it finds. The SDK clamps this wait to 60 seconds.
  5. If neither value is present or valid, the SDK uses exponential backoff instead. The delay doubles each attempt: 1 second, 2 seconds, 4 seconds, 8 seconds, up to a maximum of 60 seconds.
  6. The SDK retries the request automatically, up to maxRetries times.

Note: The Notion API repeats the Retry-After wait time under additional_data.retry_after in the error body, for clients that cannot read response headers.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
retryOnRateLimit: false, // Do not retry on 429
});

Note: retryOnRateLimit controls only 429 retries. The SDK always retries 529 Service Overload and transient 5xx server errors, up to maxRetries, no matter the value of retryOnRateLimit. This matches NotionAPIError.isRetryable().

Note: The SDK never retries a 429 response whose rateLimitReason is public_api_request_blocked, no matter the value of retryOnRateLimit. A blocked request cannot succeed on retry.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
maxRetries: 5, // Retry up to 5 times
});
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
maxRetries: 0, // Never retry
});

A 504 gateway_timeout does not guarantee that a write failed; the request may have already applied on Notion’s side. notion.pages.updateMarkdown() does not retry a 504 automatically, so you can check the page content before you retry. This does not change retry behavior for 429 or other 5xx responses on that call.

Retries do not prevent all rate limits. You might still get a rate-limited response:

import { NotionAPIError } from '@visus-io/notion-sdk-ts';
try {
await notion.pages.retrieve('page-id');
} catch (error) {
if (error instanceof NotionAPIError && error.isRateLimited()) {
console.error('Rate limited after retries');
console.error('Reason:', error.rateLimitReason);
console.error('Retry after:', error.message);
}
}

The SDK uses the native fetch API in Node 22 and later. You can provide your own implementation instead.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
// Uses native fetch by default
});

A custom fetch implementation is useful for these cases:

  • Custom logging or telemetry
  • A different HTTP client
  • Proxy support
  • Tests with mock data
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
fetch: async (url, init) => {
console.log(`Fetching: ${url}`);
const response = await fetch(url, init);
console.log(`Status: ${response.status}`);
return response;
},
});
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyAgent = new HttpsProxyAgent('http://proxy.example.com:8080');
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
fetch: (url, init) => fetch(url, { ...init, agent: proxyAgent }),
});
const mockFetch = async (url: string, init?: RequestInit) => {
return new Response(JSON.stringify({ id: 'test-id' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};
const notion = new Notion({
auth: 'test-token',
fetch: mockFetch,
});

Change the API base URL. Most projects do not need this option.

const notion = new Notion({
auth: process.env.NOTION_TOKEN,
baseUrl: 'https://api.notion.com', // Default
});

A custom base URL is useful for these cases:

  • Tests against a mock server
  • A proxy
  • Development or staging environments
const notion = new Notion({
auth: process.env.NOTION_TOKEN,
baseUrl: 'http://localhost:3000', // Local mock server
});

This example shows a fully configured client with all options:

import { Notion } from '@visus-io/notion-sdk-ts';
import { config } from 'dotenv';
config(); // Load .env
const notion = new Notion({
// Required
auth: process.env.NOTION_TOKEN!,
// API Configuration
baseUrl: 'https://api.notion.com', // Default
// Timeout Configuration
timeoutMs: 60_000, // 60 seconds (default)
// Rate Limiting & Retries
retryOnRateLimit: true, // Default
maxRetries: 3, // Default
// Custom Fetch (optional)
fetch: async (url, init) => {
// Add custom logging or a proxy
console.log(`API Call: ${url}`);
return fetch(url, init);
},
});

Configure the client differently for each environment:

const isProduction = process.env.NODE_ENV === 'production';
const notion = new Notion({
auth: process.env.NOTION_TOKEN!,
timeoutMs: isProduction ? 60_000 : 10_000, // Use a shorter timeout for development
maxRetries: isProduction ? 3 : 0, // Skip retries in development for faster feedback
fetch: isProduction
? undefined // Use default fetch
: async (url, init) => {
// Add debug logging in development
console.log(`[DEV] ${init?.method || 'GET'} ${url}`);
return fetch(url, init);
},
});