Notion is my outboard brain, and is where I keep pretty much everything related to my work and life It’s also where I write my blog posts, as it’s quick and easy.

What’s been neither very quick nor very easy has been porting them over to my CMS, as I had to re-download all the images I’d embedded into Notion and re-upload them. There were also other little fiddly bits like changing headings, and some processes I’ve settled in to, like putting the subtitle of the post first, and then adding the header image.

So I took a bit of time yesterday evening and this morning to write some code that would import content from Notion. Their API makes it pretty easy to do, and it’s well documented for such a young API.

The first thing you have to remember to do is create your API token and then give that token access to any databases you want it to see (you do that via the Share button in your Notion database). Then you need to copy the URL of the database and take out the long string of number and letters near the end of the URL, but before the ?v= bit.

Here’s an example of my database URL (not a real one):

https://www.notion.so/podcode/**a123456789012b34cd567890ef12gh34**?v=12a3456c7890123456d78e9f012g3h45

The database ID is a123456789012b34cd567890ef12gh34, so I took note of that and stored it in my CMS code.

Then I was able to query the database using the HTTP API. My filter expression looked like this:

curl -X POST
    -H 'Authorization: Bearer fake_AbCdefGHijKl1Mn2OpQ3rSTUV67Wx8yz9ab01CdeFg2'
    -H 'Content-Type: application/json'
    -H 'Notion-Version: 2021-08-16'
    -d '{"page_size": 100, "filter": {"and": [{"property": "Channels", "multi_select": {"contains": "podcode.co"}}, {"or": [{"property": "Status", "select": {"equals": "Writing"}}, {"property": "Status", "select": {"equals": "Editing"}}]}]}}'
    https://api.notion.com/v1/databases/a123456789012b34cd567890ef12gh34/query

Let’s make that query a little more readable:

{
    "filter": {
        "and": [
            {
                "property": "Channels",
                "multi_select": {
                    "contains": "podcode.co"
                }
            },
            {
                "or": [
                    {
                        "property": "Status",
                        "select": {
                            "equals": "Writing"
                        }
                    },
                    {
                        "property": "Status",
                        "select": {
                            "equals": "Editing"
                        }
                    }
                ]
            }
        ]
    }
}

This is querying my content database for posts that are destined for this website (as opposed to other places), which are either in the Writing or the Editing stage.

What I get back is a list of matching objects with IDs and names that I can display in my UI. I select the one I want to import, hit “Save”, and my code queries that specific page to get all the content blocks inside it:

curl
    -H 'Authorization: Bearer fake_AbCdefGHijKl1Mn2OpQ3rSTUV67Wx8yz9ab01CdeFg2'
    -H 'Notion-Version: 2021-08-16'
    https://api.notion.com/v1/pages/a123b456-7c23-4d56-7890-123e4f567890

I run through each block, interpret the contents and turn it into Markdown text that is then saved to the database, along with the page title. I take the first paragraph of text before the header image and use that as the post’s subtitle or excerpt, then run through each image in the Markdown copy, downloading it from Notion’s S3 bucket, uploading it to my CMS, and replacing the URL.

Pretty nifty.

And just for the meta points, this post you’re reading was written in Notion and has been imported into my CMS using the above method.

🤯