Using Notion as CMS with Eleventy

In this article we'll walk through how to use my eleventy-from-notion plugin, so you'll be able to use Notion as your CMS to use with your Eleventy setup.

At a high level, the plugin takes in a database of pages, and transforms these pages and their (front matter-like) metadata to HTML into a folder you specify during the Eleventy build process.

Privacy & security

First, a brief blurb for the data privacy-conscious. The plugin hooks into what Notion calls an "internal integration". This means that there is no additional server involved; the open-source code for the plugin is all there is. Your data leaves Notion only to end up in your statically-built Eleventy site. Furthermore, this ability is revoked as soon as you change or delete your integration secret, which is entirely under your control. In other words, there is no need to worry about your privacy or security, and if you are, you are welcome (and encouraged) to audit the plugin's source code directly.

Your pages

Before setting up the integration, let's make sure that you've got at least one page set up in Notion to be imported. At the root of it all, there should be a Notion database. This database will contain the pages to be imported into Eleventy. The columns in this database represent metadata for your pages; this is essentially front matter, and will be imported exactly as such. The names of the columns in Notion don't matter too much, as you'll be able to map each Notion column to a front matter key (even to nested keys). You're also free to create additional Notion columns, as not all columns have to be imported necessarily.

As a start (beyond the column containing your pages) it is usually a good idea to create a permalink column (of type "URL") to control where your pages end up, and a layout column if you'd like to be able to use different layouts for different pages. You might also want to create a title and description field for use within the <head> of your layouts. Alternatively (or additionally), you'll be able to use a data file in your Notion template directory within Eleventy, and dynamically generate front matter keys based on existing ones or the page's contents. That allows you to, for example, create permalinks automatically based on the title field specified in Notion.

Once you've got your database in Notion set up, let's move on to the next step: setting up your internal Notion integration.

Your internal Notion integration

The internal Notion integration is what allows the plugin to use the Notion API to import your pages. After setting up the integration, you can add it to the database page; this means the plugin will only have access to your database and the pages in it, not your entire workspace. But, we're getting ahead of ourselves - let's step through creating the integration. Notion does have documenation for this, but let's walk through it here as well.

First, head to My integrations. Next, click the nice big "New integration" button.

Next, give your integration a recognizable name (e.g. "Eleventy") and select the workspace that you want to import pages from. The "Type" should be "internal". A logo is entirely optional.

Next, let's configure our integration. We'll need to select the "content capabilities"; this plugin really only needs read access, so it is recommended to select only "Read content". For "user capabilities", you don't need to give the plugin access at all, but you can, if you want user-related information to be imported (for example for post authors). It's recommended to keep this as restricted as possible, giving it no more permissions than you need it to have.

You should now also be able to find your "Internal Integration Secret", located above the "Capabilities" section. We'll need to copy this later to pass to the plugin's configuration in Eleventy.

Plugin installation

First, let's get the elephant in the room out of the way; this plugin is hosted on JSR. You're probably using NPM (or a wrapper around it, like Yarn). I recognize this is not ideal - but not to worry, installing a JSR plugin is not too different from installing a regular NPM plugin. Instead of the regular npm install ..., you'll need one of:

# For NPM:
npx jsr add @vrugtehagel/eleventy-from-notion
# For Yarn:
yarn dlx jsr add @vrugtehagel/eleventy-from-notion
# For PNPM:
pnpm dlx jsr add @vrugtehagel/eleventy-from-notion
# For Deno:
deno add jsr:@vrugtehagel/eleventy-from-notion

This might create an .npmrc file if it didn't exist yet, which lets NPM know where to find JSR packages.

With that out of the way, and the plugin installed, we're ready to import it and configure it in our Eleventy configuration file (probably .eleventy.js or eleventy.config.js). This would look something like

import EleventyFromNotion from '@vrugtehagel/eleventy-from-notion'

export default function(eleventyConfig){
	// …
	eleventyConfig.addPlugin(EleventyFromNotion, {
		integrationSecret: '…',
	})
	// …
}

The integrationSecret should be set to the internal integration secret from Notion. While you can set this directly, for security it's best not to set this as a string directly, but instead configure it as an environment variable. If you configure the NOTION_INTEGRATION_SECRET environment variable, the plugin will pick it up automatically and you won't need to specify the integrationSecret option at all. For testing, though, we can take a bit of a shortcut just so you'll be able to see what the final result looks like.

Next, we need to tell the plugin the URL or ID to the database page. We need to add the internal integration previously created to the database page. To do so, we navigate to the database page, click the three dots in the top-right corner of the page, and find the "Connections" menu item. Then, search for whatever you named the internal integration, and select it. Confirm the connection.

Then, we need to tell the plugin which database we've added the integration to. The simplest way is to use the URL, and pass that as-is to the database option.

eleventyConfig.addPlugin(EleventyFromNotion, {
	integrationSecret: '…',
	database: 'https://notion.so/…'
})

Configuration

There's one more thing we need to get the plugin operational; the schema option. This defines a mapping between columns in the Notion database and the front matter for the imported templates. The schema option is specified as an array, each item representing the mapping of a single property.

Let's say, for this example, that we've got XXX columns defined; the main page column "Name", a "link" column, a "layout" column, a "SEO title" and an "SEO description" column. We use the following schema configuration:

eleventyConfig.addPlugin(EleventyFromNotion, {
	integrationSecret: '…',
	database: 'https://notion.so/…',
	schema: [
		{name: 'link', rename: 'permalink'},
		{name: 'layout'},
		{name: 'SEO title', rename: ['seo', 'title']},
		{name: 'SEO description', rename: ['seo', 'description']}
	]
})

The basic entries are specified as {name, rename} pairs. The name refers to the column name is Notion (this is case-sensitive!). Then rename allows you to rename the column name. If rename is not specified, like for layout in the example above, then the front matter name will match the column name exactly. If rename is specified as an array of strings, a nested front matter key is used; in the example, an seo front matter object is created with properties title and description. The eventual type of each property depends on the column type in Notion; most notably, rich text is imported as {plain, rich} objects that allow you to choose between formatted HTML strings and plain text. For example, if we want to render the SEO description inside a <meta> tag, we'll want to use seo.description.plain; if we want to render that same description as part of our document, we'd probably use seo.descroption.rich. That way, any formatting, like bold or italic text, is maintained in the article, but not rendered in the <meta> tag.

Hello, world!

Now that we've got a schema set up, the plugin should be ready to import its first page. There are a fair bit of options we could configure, but they all have defaults; in particular, the output option defaults to notion/ and specifies where the templates end up. They'll end up in your Eleventy input directory, since they are templates. The import runs once when Eleventy starts serving or building, and imports Notion pages as HTML templates into the specified output folder. The file names match the Notion page's ID (which is a UUID without the dashes).

The plugin uses inline and block formatters to turn Notion blocks and rich text into HTML. Most basic constructs are supported out of the box, but some more specific block types don't have a formatter by default. This is mostly because there is not a single agreed-upon way of formatting certain things as HTML; for example, a YouTube embed, or a todo list. If you'd like to use an unsupported block type, you'll need to specify your own block formatter, turning the relevant data into a string of HTML. Of course, you are also welcome to overwrite default formatters if you'd like to output your own custom HTML (or even to output non-HTML).

Now, when you run your Eleventy build process, your Notion pages should be included!

That's it!

And that's about it! I hope things went smoothly for you - if not, don't hesitate to create a GitHub issue so I know what to do to improve the plugin. Regardless, thanks for checking it out!