How to Handle Notion API Request Limits

Our work is reader-supported; if you buy through our links, we may earn an affiliate commission.

If you’ve worked with the Notion API, you might have run into a situation where your code fails due to the API’s request limits, leaving you scratching your head.

I won’t bury the lede: I’ve solved this problem for you. In this post, I’ll show you a free, open-source tool I’ve created that completely handles these request limits for you.

But first, a bit of background.

If you didn’t know, the API has a pretty long list of limitations on the types of requests you can make, and how often you can make them. Most of them have to do with the size of requests that create new pages, append block children to existing pages or blocks, or edit data source property values.

For example, when you’re creating a new page, your initial API request has several limits. Here are a handful:

  1. The request can have no more than 1,000 blocks total
  2. Any individual array of blocks can have no more than 100 elements
  3. The whole request must be under 500KB
  4. Any nested blocks (e.g. nested bullet lists) can only go two levels deep

These limitations can make it difficult to work with large or complex payloads – like the text of a very long article, or the body of an email with deeply nested reply chains.

They’re also why you may have experienced failures when working with Notion via no-code tools like Zapier or Make.com. These tools use the Notion API, and not all of them are optimized to deal with these limitations.

So, how do you deal with these limitations? Simple. You split large payloads into multiple chunks, then make multiple requests.

For example, if you’ve converted a long article into 200 paragraph blocks, you’d put the first 100 into the initial Create Page request, then append the other 100 in a subsequent Append Block Children request.

Like I said: Simple!

…until it’s not.

In the real world, your applications and automations will end up dealing with far more complex payloads. For example, our web clipper Flylighter lets you capture full articles from any web pages, and accurately renders everything from nested bullet lists to code blocks to HTML tables.

This makes dealing with API request limits way more complicated.

What if one of those 200 paragraphs contains a really, really long sentence? Now it hits the API’s 2,000-character limit on Rich Text Objects.

What if there’s a bullet list with nested bullets down to five levels? Good luck capturing that: The API only allows up to two levels of nested children arrays per request.

Eventually, your code becomes a rat’s nest of random functions and failsafes to deal with all of these edge cases. Ask me how I know.

Luckily for you, I could not put this problem down – so now there’s a better way.

To solve this problem, I created a free, open-source (MIT license) JavaScript library called Notion-Helper.

You can think of Notion-Helper as an API on top of the API. The library’s primary purpose is to make it easier to build the JSON objects that form the body of most Notion API requests that create pages, append blocks, and set property values.

For example, this is how you’d normally create a paragraph block using the vanilla API:

const paragraphBlock = {
    type: "paragraph",
    rich_text: [
        {
            type: "text",
            text: {
                content: "Hello, world!"
            }
        }
    ]
}
Code language: TypeScript (typescript)

Here’s how you do it with Notion-Helper:

import { paragraph } from "notion-helper";
const paragraphBlock = paragraph("Hello, world!");
Code language: TypeScript (typescript)

This was my initial goal with the library. I wanted to create a set of easy-to-use shorthand functions that vastly reduced the amount of code you needed to write to create any block.

However, as I built on top of this foundation and added more capabilities to the library, I realized that it could also be helpful as a tool for splitting large payloads into multiple requests.

Thus, I added two powerful functions to the library, which are the focus of this post:

  1. createPage()
  2. appendBlocks()

These two functions are actually aliases of the two primary methods in the library’s request API:

  1. request.pages.create()
  2. request.blocks.children.append()

Together, these two methods completely take care of the Notion API’s request limits for you – at least the ones related to all aspects of payload length/size. (They won’t do anything about rate-limiting)

Both methods have complex internal logic that I’ve built to split payloads into the minimum number of separate requests possible in order to stay within the API’s limits.

This means you can simply pass a large payload – thousands of blocks, even – just as you would if you were going to make a single request to the vanilla SDK. These methods do all the splitting work for you.

They’ve been battle-tested on some truly huge payloads. I typically test library updates by capturing the entirety of Moby Dick to a Notion page.

Here’s how to use the createPage() function.

First, let’s generate a truly ridiculous payload using Notion-Helper’s other stupendously useful feature: The createNotionBuilder() function, which gives you a fluent interface for building out payloads.

Once that’s done, we just await createPage(), passing a single argument: An object with a data key with our build page body, and a client key with a client object created by the Notion SDK (or, optionally, your own HTTP request function – e.g. one created with fetch or ky):

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

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

const data_source_id = "YOUR_DB_ID";

let page = createNotionBuilder({
    limitChildren: false,
})
    .parentDataSource(data_source_id)
    .title("Name", "Page with a big list!")
    .heading1("Down the Rabbit Hole...")

for (let i = 0; i < 1000; i++) {
    page = page.paragraph(`This is paragraph #${i + 1}.`);
}

page = page.build();

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

console.log(response);
Code language: TypeScript (typescript)

As you can see, this code creates 1,000 paragraph blocks in the page body, and they’re all in the main children array.

Since the API limits any children array to 100 elements, you’d need to split this into 10 API requests normally. However, createPage() just does this for you.

Internally, createPage() uses your passed client object to make the first request, which creates the page. From there, it kicks the remaining block chunks over to appendBlocks(), which appends them to the newly-created page.

I’ll update this blog post with more detail later, but for now I’ll simply point your attention to the Notion-Helper website where you can see more examples.

Even if you’re already using an LLM to quickly generate page body objects, I think you’ll find the createPage() and appendBlocks() functions in Notion-Helper to be really helpful in building more robust applications that work with Notion.

As an API nerd, I’m really proud of this project, and I hope you find it helpful. I’ll also note that Pipedream is now using it to power their own Notion actions!

🤔 Have an UB Question?

Fill out the form below and I’ll answer as soon as I can! ~Thomas

🤔 Have a Question?

Fill out the form below and I’ll answer as soon as I can! ~Thomas