Power tools for the Notion API

notion-helper is a Node.js library that makes working with the Notion API a breeze.

Easy to learn, zero dependencies, open-source, full JSDoc for IntelliSense.

Built by Thomas Frank.

Let's say you've got a JSON object that you want to turn into a page in a Notion database.

const album = {
    name: "A Pretty Face to Ruin Everything",
    artist: "Airplane Mode",
    release_date: "03/14/2020",
    cover: "https://i.imgur.com/d3BBFhF.jpeg",
    tracks: [
        "When the Lights Go Out",
        "West Coast",
        "Candy Store",
        "Pedestal",
        "She's Asleep",
        "The Ledge, Pt. 1",
        "Anastasia",
        "For the Moment",
        "I Know",
        "While My Guitar Gently Weeps",
        "The Ledge, Pt. 2",
        "Both Can Be True",
        "Forever, Again",
        "Everlong",
    ],
};
const album = {
    name: "A Pretty Face to Ruin Everything",
    artist: "Airplane Mode",
    release_date: "03/14/2020",
    cover: "https://i.imgur.com/d3BBFhF.jpeg",
    tracks: [
        "When the Lights Go Out",
        "West Coast",
        "Candy Store",
        "Pedestal",
        "She's Asleep",
        "The Ledge, Pt. 1",
        "Anastasia",
        "For the Moment",
        "I Know",
        "While My Guitar Gently Weeps",
        "The Ledge, Pt. 2",
        "Both Can Be True",
        "Forever, Again",
        "Everlong",
    ],
};
A Notion database page with the album's information.
A Notion database page with the album's information.
A Notion database page with the album's information.

notion-helper's createNotion() function allows you to build page objects with far less code.

It'll create multiple blocks from an array with loop(), handle date strings for you, and a lot more.

With notion-helper:

// Install the package

npm install notion-helper

// Use it in your code

import { createNotion } from "notion-helper";

const page = createNotion()
    .parentDb(database_id)
    .title("Name", album.name) // prop name, value
    .richText("Artist", album.artist)
    .date("Released", album.release_date)
    .heading1("Tracklist")
    .loop("numbered_list_item", album.tracks)
    .heading1("Album Art")
    .image(album.cover)
    .build(); // call build() at the end of the chain

// Send page.content as the request body
const response = notion.pages.create(page.content);
// Install the package

npm install notion-helper

// Use it in your code

import { createNotion } from "notion-helper";

const page = createNotion()
    .parentDb(database_id)
    .title("Name", album.name) // prop name, value
    .richText("Artist", album.artist)
    .date("Released", album.release_date)
    .heading1("Tracklist")
    .loop("numbered_list_item", album.tracks)
    .heading1("Album Art")
    .image(album.cover)
    .build(); // call build() at the end of the chain

// Send page.content as the request body
const response = notion.pages.create(page.content);

Without notion-helper:

const listItems = album.tracks.map((item) => ({
    numbered_list_item: {
        rich_text: [
            {
                text: {
                    content: item,
                },
            },
        ],
    },
}));

function getISODate(dateString) {
    return new Date(dateString).toISOString();
}

const page = {
    parent: {
        database_id: database_id,
    },
    properties: {
        Name: {
            title: [
                {
                    text: {
                        content: album.name,
                    },
                },
            ],
        },
        Artist: {
            rich_text: [
                {
                    text: {
                        content: album.artist,
                    },
                },
            ],
        },
        Released: {
            date: {
                start: getISODate(album.release_date),
            },
        },
    },
    children: [
        {
            heading_1: {
                rich_text: [
                    {
                        text: {
                            content: "Tracklist",
                        },
                    },
                ],
            },
        },
        ...listItems,
        {
            heading_1: {
                rich_text: [
                    {
                        text: {
                            content: "Album Art",
                        },
                    },
                ],
            },
        },
        {
            image: {
                external: {
                    url: album.cover,
                },
            },
        },
    ],
};

const response = notion.pages.create(page);
const listItems = album.tracks.map((item) => ({
    numbered_list_item: {
        rich_text: [
            {
                text: {
                    content: item,
                },
            },
        ],
    },
}));

function getISODate(dateString) {
    return new Date(dateString).toISOString();
}

const page = {
    parent: {
        database_id: database_id,
    },
    properties: {
        Name: {
            title: [
                {
                    text: {
                        content: album.name,
                    },
                },
            ],
        },
        Artist: {
            rich_text: [
                {
                    text: {
                        content: album.artist,
                    },
                },
            ],
        },
        Released: {
            date: {
                start: getISODate(album.release_date),
            },
        },
    },
    children: [
        {
            heading_1: {
                rich_text: [
                    {
                        text: {
                            content: "Tracklist",
                        },
                    },
                ],
            },
        },
        ...listItems,
        {
            heading_1: {
                rich_text: [
                    {
                        text: {
                            content: "Album Art",
                        },
                    },
                ],
            },
        },
        {
            image: {
                external: {
                    url: album.cover,
                },
            },
        },
    ],
};

const response = notion.pages.create(page);

createNotion() has other tricks up its sleeve, too…

Add supported options to blocks, like colors or a children array. In rich_text, build a rich text object from scratch with custom annotations.

Look familiar? Every block method can accept an object with the same property names from the Notion API itself. Here's an example.

Add supported options to blocks, like colors or a children array. In rich_text, build a rich text object from scratch with custom annotations.

Look familiar? Every block method can accept an object with the same property names from the Notion API itself. Here's an example.

import { createNotion, buildRichTextObj } from "notion-helper";

const page = createNotion()
    .parentDb(database_id)
    .title("Name", album.name)
    .heading1({
        rich_text: buildRichTextObj(
            "Tracklist",
            {
                color: "red",
                italic: true,
            }
        ),
        color: "green_background",
    })
    .build();

Add child blocks to any supported block by defining it with startParent(). This lets you build tables!

Pass a callback to loop() instead of a block type to define custom handling.

P.S. – See the number in that tracks object? Normally, the API will pitch a fit if you try to put that in a table cell. But notion-helper will just coerce it to a string for you. 🧶

import { createNotion } from "notion-helper";

const album = {
    name: "Mandatory Fun",
    artist: `"Weird Al" Yankovic`,
    release_date: "07/15/2014",
    tracks: [
        {
            "No.": 1,
            Title: "Handy",
            "Writer(s)":
                "Amethyst Kelly\nCharlotte Aitchison...",
            Length: "2:56",
        },
        /* ...more tracks... */
    ],
};

const page = createNotion()
    .parentDb(database_id)
    .title("Name", album.name)
    .heading1("Tracklist")
    .startParent("table", {
        has_column_header: true,
        rows: [["No", "Title", "Writer(s)", "Length"]],
    })
    .loop((page, track) => {
        page.tableRow([
            track["No."], track.Title, track["Writer(s)"], track.Length
        ])
    }, album.tracks)
    .endParent()
    .build();

The Notion API only supports 100 blocks in any block array sent in a single request.

createNotion() returns an additionalBlocks property with additional block chunks. Loop over this with Append Block Children calls to build pages with hundreds or thousands of blocks.

import { createNotion } from "notion-helper";

// Build a transcript array with 300 lines
const transcript = []

for (let i = 0; i < 300; i++) {
    const line = `This is sentence #${i}.`
    transcript.push(line)
}

// Create the page in Notion
const page = createNotion()
    .parentDb(database_id)
    .title("Name", "Long Transcript")
    .loop("paragraph", transcript)
    .build()

const pageResponse = await notion.pages.create(page.content)

for (let chunk of page.additionalBlocks) {
    const appended = await notion.blocks.children.append({
        block_id: pageResponse.id,
        children: chunk
    })
}

createNotion() can build full page objects, or it can go more granular and return a property object or an array of blocks you can use in a children property.

Simply add parentDb(), parentPage(), pageId(), or blockId() to the chain to create a page/block object.

Add only property methods to return a property object. (Examples: title(), richText(), relation(), date()…)

Add only block methods to return an array of blocks. (Examples: heading1(), paragraph(), bulletListItem()…)

import { createNotion } from "notion-helper";

// Create full page objects with a parent property, for page creation...
const page = createNotion().parentDb(database_id);

// or
const page = createNotion().parentPage(page_id);

// Create a page object with a page_id property, for updating props or doing reads...
const page = createNotion().pageId(page_id);

// Create a block object with a block_id property,
// for updating blocks or appending children...
const block = createNotion().blockId(block_id);

// Create a property object
const props = createNotion().title("Name", "Page title");

// Create an array of blocks
const blocks = createNotion().heading1("This is a heading")
    .paragraph("This is a paragraph!")

createNotion() includes methods for building/setting any kind of supported object, property, or block. It also includes some generic methods for fine-grained control.

Property Methods:

  • property(name, type, value)

  • title(name, value)

  • richText(name, value)

  • checkbox(name, value)

  • date(name, start, end)

  • email(name, value)

  • files(name, fileArray)

  • multiSelect(name, valuesArray)

  • number(name, value)

  • people(name, personArray)

  • phoneNumber(name, value)

  • relation(name, pageArray)

  • select(name, value)

  • status(name, value)

  • url(name, value)

Block Methods:

  • addBlock(blockType, options)

  • startParent(blockType, options)

  • endParent()

  • paragraph(options)

  • heading1(options)

  • heading2(options)

  • heading3(options)

  • bulletedListItem(options)

  • bullet(options) - alias function

  • numberedListItem(options)

  • num(options) - alias function

  • toDo(options)

  • toggle(options)

  • code(options)

  • quote(options)

  • callout(options)

  • divider()

  • image(options)

  • video(options)

  • file(options)

  • pdf(options)

  • bookmark(options)

  • embed(options)

  • tableOfContents(options)

  • table(options)

  • tableRow(options)

  • loop(blockTypeOrCallback, options)

Page/Block Object Methods:

  • parentDb()

  • parentPage()

  • pageId()

  • propertyId()

  • blockId()

  • cover()

  • icon()

Utility Methods:

  • build() - creates the final object.

  • reset() - resets the builder.

Need more control? Import the other functions.

notion-helper is a ground-up library that offers syntactic sugar at multiple levels. Everything at the highest levels (like createNotion() or quickPages()) builds off the lower-level functions.

This also means notion-helper truly is a helper for the Notion API, not a replacement SDK.

Build rich text objects from the ground up by importing buildRichTextObj, then passing a string (or objects for Mentions). Optionally pass an annotation object, URL, and type.

import { buildRichTextObj } from 'notion-helper'

const line = "I will be king of the pirates!"

const richText = buildRichTextObj(line)

const higlighted = buildRichTextObj(line, { color: "yellow_background" })

const linked = buildRichTextObj(line, {}, "https://en.wikipedia.org/wiki/Monkey_D._Luffy")

// Or build equations

const equation = "\frac{{ - b \pm \sqrt {b^2 - 4ac} }}{{2a}}"

const richEquation = buildRichTextObj(equation, {}, null, "equation")

Build blocks by importing block. This object exposes a createBlock() method for each supported block type.

Each block type also has a shorthand function you can import.

import { block } from 'notion-helper'
import { buildRichTextObj } from 'notion-helper'

const image = "https://i.imgur.com/5vSShIw.jpeg"

const imageBlock = block.image.createBlock(image)

// or add a caption...

const captionedImageBlock = block.image.createBlock({
    url: image,
    caption: "A dog wearing sunglasses"
})

// caption coerces to rich text, but you can also pass your own!
// buildRichTextObj() always returns an array, so make use of flat()

const richCaptionedImageBlock = block.image.createBlock({
    url: image,
    caption: [ 
        buildRichTextObj(
            "A dog wearing sunglasses ",
        ),
        buildRichTextObj(
            "(Source)",
            {},
            "https://i.imgur.com/5vSShIw.jpeg"
        )
    ].flat()
})

// block.image.createBlock() feel too verbose? Import the shorthand function!
// Every block has one, and they have the same names as the methods in createNotion()
import { image } from 'notion-helper'

const anotherImageBlock = image(image)

// You can also just import NotionHelper to get all the methods, and to preserve a separate namespace

import NotionHelper from 'notion-helper'

const thirdImageBlock = NotionHelper.image(image)

Build property value or page meta objects by importing page_props and page_meta respectively.

page_props exposes setProp() methods for writeable properties.
page_meta exposes createMeta() methods for create parent, page_id, block_id, property_id, cover, and icon objects.

As with blocks, there are shorthand methods you can import.

import { page_props, page_meta } from 'notion-helper'

// Create a parent database object:

const database_id = "41e42f70a1ec4a6c917045f4ed6c930a"

const parent = page_meta.parent.createMeta({
    id: database_id,
    type: "database_id"
})

// Or do it faster wth a shorthand function

import { parentDb } from 'notion-helper'

const parent = parentDb(database_id)

// Set a date property's value:

const startDate = "09/13/2024" // MM/DD/YYYY dates get auto-converted!

const dateProp = page_props.date.setProp(startDate)

// Or do it faster with a shorthand function,
// and pass an end date while you're at it

import { date } from 'notion-helper'

const endDate = "09/17/2024"

const dateProp = date(startDate, endDate)

What about actually sending API requests?

notion-helper can also send API requests for you, and it has internal logic to handle the Notion API's nesting limits and block limits.

Use notion-helper's request API to access methods that mirror the actual API - e.g. request.pages.create(). Or import shorthand functions like createPage() and appendBlocks().

Pass in a valid Client object from the official Notion SDK, or define your own HTTP request functions and pass them as callbacks.

The BYO-HTTP functions philosophy keeps notion-helper light as a feather, with zero dependencies 😉

In this example, blocks.content will contain an array of child blocks that goes 10 levels deep.

The Notion API would reject this as a single request due to nesting limits (described here).

But pass that array to appendBlocks() along with the parent block_id (which can be a page) and a valid notion Client object, and it'll take care of everything for you.

import { Client } from "@notionhq/client";
import { appendBlocks, createNotion } from "notion-helper";

const secret = "YOUR_NOTION_KEY";
const notion = new Client({ auth: secret });

let blocks = createNotion({
    nestingLimit: 100, // Set a high nesting limit
    limitChildren: false,
});

for (let i = 0; i < 10; i++) {
    blocks = blocks.startParent(
        "bulleted_list_item", 
        `Level ${i + 1}`
    );
}

blocks = blocks.build();

const page_id = "YOUR_PAGE_ID";

const response = await appendBlocks({
    block_id: page_id,
    children: blocks.content,
    client: notion,
});

Don't want to use the official Notion SDK?

Instead of passing a client object, you can pass an apiCall function.

This lets you define your own custom function for handling the actual HTTP requests. Use ky, got, node-fetch, or anything else you like.

When you do this, you should also pass a getResults function that can grab the results array returned from the Notion API.

import ky from 'ky';
import { appendBlocks, createNotion } from "notion-helper";

const secret = "YOUR_NOTION_KEY";

const apiCall = async (block_id, data) => {
    return await ky.patch(`https://api.notion.com/v1/blocks/${block_id}/children`, {
        json: data,
        headers: {
            'Authorization': `Bearer ${secret}`,
            'Notion-Version': "2022-06-28",
            'Content-Type': 'application/json',
        },
    }).json();
};

let blocks = createNotion({ 
    nestingLimit: 100, limitChildren: false 
});

for (let i = 0; i < 10; i++) {
    blocks = blocks
        .startParent("bulleted_list_item", `Level ${i+1}`);
}

blocks = blocks.build();

const page_id = "YOUR_PAGE_ID";
const response = await appendBlocks({
    block_id: page_id,
    children: blocks.content,
    apiCall: (block_id, children) => apiCall(`/blocks/${block_id}/children`, { children }),
    getResults: (response) => response.results // Adjust to response structure
});

Want to create a page that contains all those blocks instead?

Import createPage(). Similar to appendBlocks(), you can pass it your data object and Client object.

And just like appendBlocks(), it'll handle large block arrays and deeply nested objects for you.

If you're rolling with a custom apiCall function, give it a getPage function and getResults function as well – these will handle the create-page response and the append-block responses respectively.

import { Client } from "@notionhq/client";
import { createPage, createNotion } from "notion-helper";

const secret = "YOUR_NOTION_KEY";
const notion = new Client({ auth: secret });

const database_id = "YOUR_DB_ID";

let page = createNotion({
    nestingLimit: 100,
    limitChildren: false,
})
    .parentDb(database_id)
    .title("Name", "Page with a big list!")
    .heading1("Down the Rabbit Hole...")

for (let i = 0; i < 10; i++) {
    page = page.startParent(
        "bulleted_list_item", 
        `Level ${i + 1}`
    );
}

page = page.build();

const response = await createPage({
    data: page.content,
    client: notion
});

Don't understand the Notion API quite yet?

No worries, watch this free crash course and you'll be building your own automations in in no time.

notion-helper will make it easier to work with the Notion API, but it's still very helpful if you understand how it the API works.

Made with 🍚 and 🌮 and other stuff by Thomas Frank