DEV Community

Chris Cook
Chris Cook

Posted on • Edited on • Originally published at zirkelc.dev

Convert HTML To Notion Blocks

If you need to import HTML into Notion, you may have faced the challenge of converting HTML to Notion's block format. In my recent project, I stumbled upon this issue and was unable to find any existing package that provided a solution. That is why in this post I want to share the code and libraries that helped me make it work.

The first step is to parse the HTML code into a JavaScript object. To do this, we use the fromHtml function from the hast-util-from-html library, which takes an HTML string as input and returns a hast object:

import { fromHtml } from 'hast-util-from-html';

const html = '<h1>Hello <strong>world!</strong></h1>';
// HTML to HTML AST
const hast = fromHtml(html, { fragment: true });
Enter fullscreen mode Exit fullscreen mode

The hast object represents the HTML code as an abstract syntax tree (AST) that can be manipulated and transformed into other formats:

{
  type: 'root',
  children: [
    {
      type: 'element',
      tagName: 'h1',
      properties: {},
      children: [
        {
          type: 'text',
          value: 'Hello ',
          position: {
            start: { line: 1, column: 5, offset: 4 },
            end: { line: 1, column: 11, offset: 10 }
          }
        },
        {
          type: 'element',
          tagName: 'strong',
          properties: {},
          children: [
            {
              type: 'text',
              value: 'world!',
              position: {
                start: { line: 1, column: 19, offset: 18 },
                end: { line: 1, column: 25, offset: 24 }
              }
            }
          ],
          position: {
            start: { line: 1, column: 11, offset: 10 },
            end: { line: 1, column: 34, offset: 33 }
          }
        }
      ],
      position: {
        start: { line: 1, column: 1, offset: 0 },
        end: { line: 1, column: 39, offset: 38 }
      }
    }
  ],
  data: { quirksMode: false },
  position: {
    start: { line: 1, column: 1, offset: 0 },
    end: { line: 1, column: 39, offset: 38 }
  }
}
Enter fullscreen mode Exit fullscreen mode

Once the HTML code is parsed into a hast object, the next step is to convert it to Markdown. We use the toMdast function from the hast-util-to-mdast library, which takes a hast object as input and returns a mdast object.

import { fromHtml } from 'hast-util-from-html';
import { toMdast } from 'hast-util-to-mdast';

const html = '<h1>Hello <strong>world!</strong></h1>';
const hast = fromHtml(html, { fragment: true });
// HTML AST to Markdown AST
const mdast = toMdast(hast);
Enter fullscreen mode Exit fullscreen mode

The mdast object represents the Markdown code as an AST, similar to the previous HTML AST:

{
  type: 'root',
  children: [
    {
      type: 'heading',
      depth: 1,
      children: [
        {
          type: 'text',
          value: 'Hello ',
          position: {
            start: { line: 1, column: 5, offset: 4 },
            end: { line: 1, column: 11, offset: 10 }
          }
        },
        {
          type: 'strong',
          children: [
            {
              type: 'text',
              value: 'world!',
              position: {
                start: { line: 1, column: 19, offset: 18 },
                end: { line: 1, column: 25, offset: 24 }
              }
            }
          ],
          position: {
            start: { line: 1, column: 11, offset: 10 },
            end: { line: 1, column: 34, offset: 33 }
          }
        }
      ],
      position: {
        start: { line: 1, column: 1, offset: 0 },
        end: { line: 1, column: 39, offset: 38 }
      }
    }
  ],
  position: {
    start: { line: 1, column: 1, offset: 0 },
    end: { line: 1, column: 39, offset: 38 }
  }
}
Enter fullscreen mode Exit fullscreen mode

The next step is to serialize Markdown AST into a Markdown string. We use the toMarkdown function from the mdast-util-to-markdown library, which takes a mdast object as input and returns Markdown string:

import { fromHtml } from 'hast-util-from-html';
import { toMdast } from 'hast-util-to-mdast';
import { toMarkdown } from 'mdast-util-to-markdown';

const html = '<h1>Hello <strong>world!</strong></h1>';
const hast = fromHtml(html, { fragment: true });
const mdast = toMdast(hast);
// Markdown AST to Markdown string
const markdown = toMarkdown(mdast);
Enter fullscreen mode Exit fullscreen mode

The Markdown string has the format we are used to from Editors like on Dev.to or GitHub:

# Hello **world!**
Enter fullscreen mode Exit fullscreen mode

The final step is to convert the Markdown string into Notion block objects that can be used to create new blocks using the Notion API. We use the markdownToBlocks function from the @tryfabric/martian library, which takes the Markdown string as input and returns an array of BlockObjectRequest objects:

import { fromHtml } from 'hast-util-from-html';
import { toMdast } from 'hast-util-to-mdast';
import { toMarkdown } from 'mdast-util-to-markdown';
import { markdownToBlocks } from '@tryfabric/martian';

const html = '<h1>Hello <strong>world!</strong></h1>';
const hast = fromHtml(html, { fragment: true });
const mdast = toMdast(hast);
const markdown = toMarkdown(mdast);
// Markdown string to Notion Blocks
const blocks = markdownToBlocks(markdown);
Enter fullscreen mode Exit fullscreen mode

Each BlockObjectRequest object represents a Notion block and contains the necessary information to create and format the block in your Notion page:

[
  {
    object: 'block',
    type: 'heading_1',
    heading_1: {
      rich_text: [
        {
          type: 'text',
          annotations: {
            bold: false,
            strikethrough: false,
            underline: false,
            italic: false,
            code: false,
            color: 'default'
          },
          text: { content: 'Hello ', link: undefined }
        },
        {
          type: 'text',
          annotations: {
            bold: true,
            strikethrough: false,
            underline: false,
            italic: false,
            code: false,
            color: 'default'
          },
          text: { content: 'world!', link: undefined }
        }
      ]
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Here is an example of how to create a new page in your Notion workspace with the blocks converted from the original HTML. We use the @notionhq/client library and you'll need to have a valid Notion API key and database ID:

import { Client } from '@notionhq/client';

// ...
const blocks = markdownToBlocks(markdown);

const notion = new Client({ auth: process.env.NOTION_API_KEY });

const properties = {
  Name: {
    title: [{ text: { content: 'New Page' } }]
  }
};

const response = await notion.pages.create({
  parent: { database_id: process.env.NOTION_DATABASE_ID },
  properties,
  children: blocks,
});
Enter fullscreen mode Exit fullscreen mode

The response from Notion contains the new page and its properties. However, it does not contain the blocks, since these must be queried separately.

{
  object: 'page',
  id: 'c7d50cb4-0518-4ef9-8457-056a53fd53eb',
  created_time: '2023-03-01T10:28:00.000Z',
  last_edited_time: '2023-03-01T10:28:00.000Z',
  created_by: { object: 'user', id: '9d72c6df-be87-4bb5-8edb-1c19461d01bb' },
  last_edited_by: { object: 'user', id: '9d72c6df-be87-4bb5-8edb-1c19461d01bb' },
  cover: null,
  icon: null,
  parent: {
    type: 'database_id',
    database_id: '95970798-23cc-4f7c-a0c5-4870b80fcdee'
  },
  archived: false,
  properties: {
    Name: {
      id: 'title',
      type: 'title',
      title: [
        {
          type: 'text',
          text: { content: 'New Page', link: null },
          annotations: {
            bold: false,
            italic: false,
            strikethrough: false,
            underline: false,
            code: false,
            color: 'default'
          },
          plain_text: 'New Page',
          href: null
        }
      ]
    },
    // ...
  },
  url: 'https://www.notion.so/New-Page-c7d50cb405184ef98457056a53fd53eb'
}
Enter fullscreen mode Exit fullscreen mode

I hope you found this post helpful. If you have any questions or comments, feel free to leave them below. If you'd like to connect with me, you can find me on LinkedIn or GitHub. Thanks for reading!

Top comments (3)

Collapse
 
niklampe profile image
Nik

So simple and elegant. Great work!

Collapse
 
artu_hnrq profile image
Arthur Henrique

It was what I was looking for
Thanks!

Collapse
 
zirkelc profile image
Chris Cook

Glad it helps! :)