Useful utilities for working with Notion data structures and operations in both Node.js and browser environments.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Tools for building navigation elements and extracting structural information from Notion pages including table of contents generation and workspace traversal.
Generates a table of contents by parsing H1, H2, and H3 header elements from a page.
/**
* Gets the metadata for a table of contents block by parsing the page's H1, H2, and H3 elements
* @param page - The page block to analyze
* @param recordMap - Extended record map containing all blocks
* @returns Array of TOC entries with hierarchy information
*/
function getPageTableOfContents(
page: PageBlock,
recordMap: ExtendedRecordMap
): Array<TableOfContentsEntry>;
interface TableOfContentsEntry {
/** Block ID of the header */
id: string;
/** Block type (header_1, header_2, header_3) */
type: BlockType;
/** Header text content */
text: string;
/** Indentation level (0, 1, 2) */
indentLevel: number;
}Usage Example:
import { getPageTableOfContents } from "notion-utils";
const toc = getPageTableOfContents(pageBlock, recordMap);
toc.forEach(entry => {
const indent = " ".repeat(entry.indentLevel);
console.log(`${indent}- ${entry.text} (${entry.type})`);
});
// Example output:
// - Introduction (header_1)
// - Overview (header_2)
// - Getting Started (header_2)
// - Installation (header_3)
// - Advanced Topics (header_1)Performs a traversal over a Notion workspace to collect all reachable pages.
/**
* Performs a traversal over a Notion workspace starting from a seed page
* @param rootPageId - Starting page ID for traversal
* @param rootSpaceId - Space ID containing the pages (can be undefined)
* @param getPage - Function to fetch page data by ID
* @param options - Traversal configuration options
* @returns Promise resolving to a map of all discovered pages
*/
function getAllPagesInSpace(
rootPageId: string,
rootSpaceId: string | undefined,
getPage: (pageId: string) => Promise<ExtendedRecordMap>,
options?: TraversalOptions
): Promise<PageMap>;
interface TraversalOptions {
/** Number of concurrent page requests (default: 4) */
concurrency?: number;
/** Whether to traverse collection database items (default: true) */
traverseCollections?: boolean;
/** Optional target page ID to stop at when found */
targetPageId?: string;
/** Maximum traversal depth (default: Infinity) */
maxDepth?: number;
}
type PageMap = Record<string, ExtendedRecordMap>;Usage Example:
import { getAllPagesInSpace } from "notion-utils";
// Function to fetch a page (you provide this)
async function fetchPage(pageId: string): Promise<ExtendedRecordMap> {
// Your implementation to fetch page data
return await notion.getPage(pageId);
}
// Traverse workspace starting from a root page
const allPages = await getAllPagesInSpace(
"root-page-id",
"space-id",
fetchPage,
{
concurrency: 6,
traverseCollections: true,
maxDepth: 10
}
);
console.log(`Found ${Object.keys(allPages).length} pages`);
// Access individual pages
Object.entries(allPages).forEach(([pageId, recordMap]) => {
const title = getPageTitle(recordMap);
console.log(`Page ${pageId}: ${title}`);
});Gets all block IDs contained within a page, including nested content.
/**
* Gets the IDs of all blocks contained on a page starting from a root block ID
* @param recordMap - Extended record map containing all blocks
* @param blockId - Starting block ID (uses first block if not provided)
* @returns Array of all block IDs found on the page
*/
function getPageContentBlockIds(recordMap: ExtendedRecordMap, blockId?: string): string[];Usage Example:
import { getPageContentBlockIds } from "notion-utils";
// Get all block IDs for a page
const blockIds = getPageContentBlockIds(recordMap);
console.log(`Page contains ${blockIds.length} blocks`);
// Get blocks starting from specific block
const childBlockIds = getPageContentBlockIds(recordMap, "specific-block-id");
console.log(`Section contains ${childBlockIds.length} blocks`);
// Use block IDs to access individual blocks
blockIds.forEach(blockId => {
const block = recordMap.block[blockId]?.value;
if (block) {
console.log(`Block ${blockId}: ${block.type}`);
}
});// Re-exported from notion-types
interface ExtendedRecordMap {
block: Record<string, Block>;
collection?: Record<string, Collection>;
collection_view?: Record<string, CollectionView>;
notion_user?: Record<string, NotionUser>;
collection_query?: Record<string, any>;
signed_urls?: Record<string, string>;
preview_images?: Record<string, string>;
}
interface PageBlock extends Block {
type: "page";
properties?: {
title?: Decoration[];
};
format?: {
page_icon?: string;
page_cover?: string;
page_cover_position?: number;
page_full_width?: boolean;
page_small_text?: boolean;
};
}
// Navigation-specific types
interface TableOfContentsEntry {
id: string;
type: BlockType;
text: string;
indentLevel: number;
}
interface TraversalOptions {
concurrency?: number;
traverseCollections?: boolean;
targetPageId?: string;
maxDepth?: number;
}
type PageMap = Record<string, ExtendedRecordMap>;
type BlockType = 'header_1' | 'header_2' | 'header_3' | 'text' | 'bulleted_list' | 'numbered_list' | 'page' | /* ... many other block types */;Install with Tessl CLI
npx tessl i tessl/npm-notion-utils