Fahad/
RESUME ONLINESTABLE_BUILD
arrow_backBACK TO BLOG
2026-03-27CMSNOTIONAI

Notion integration as a CMS (with code snippets)

https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&fit=crop&w=2400&q=80

Why use Notion as a CMS?

Notion is a great place to write because it’s fast, familiar, and collaborative. Using it as a CMS means:

  • You edit content in Notion
  • Your app pulls structured fields (title, slug, date, tags, excerpt)
  • Your site renders the page content as HTML/Markdown

This works especially well for:

  • Personal blogs
  • Product docs
  • Changelogs
  • Simple marketing pages

The Notion setup (the content model)

A simple “Blog” database is usually enough. Typical properties:

  • Name (title)
  • Slug (text)
  • Date (date)
  • Tags (multi-select)
  • Published (checkbox)
  • Excerpt (text)

Tip: Keep “Slug” unique. It becomes your stable URL.


Architecture overview

Your app usually has two jobs:

  1. Fetch a list of posts (for the home page / archive)
  2. Fetch a single post by slug (for the post page)

A common flow:

  • Query database rows where Published = true
  • Sort by Date desc
  • Render previews using Name + Excerpt
  • On the post page, load the Notion page content and render it

Code snippet: Environment variables

Keep secrets out of code.

# .env
NOTION_API_KEY=secret_xxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Code snippet: Fetch published posts (Node.js)

This uses the official Notion SDK.

import { Client } from "@notionhq/client";
 
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const databaseId = process.env.NOTION_DATABASE_ID;
 
export async function getPublishedPosts() {
	const res = await notion.databases.query({
		database_id: databaseId,
		filter: {
			property: "Published",
			checkbox: { equals: true },
		},
		sorts: [
			{
				property: "Date",
				direction: "descending",
			},
		],
	});
 
	return res.results.map((page) => {
		const props = page.properties;
		return {
			id: page.id,
			title: props.Name.title?.[0]?.plain_text ?? "Untitled",
			slug: props.Slug.rich_text?.[0]?.plain_text ?? "",
			excerpt: props.Excerpt.rich_text?.[0]?.plain_text ?? "",
			date: props.Date.date?.start ?? null,
			tags: props.Tags.multi_select?.map((t) => t.name) ?? [],
		};
	});
}

Code snippet: Fetch a post by slug

export async function getPostBySlug(slug) {
	const res = await notion.databases.query({
		database_id: databaseId,
		filter: {
			and: [
				{ property: "Published", checkbox: { equals: true } },
				{ property: "Slug", rich_text: { equals: slug } },
			],
		},
	});
 
	return res.results?.[0] ?? null;
}

Rendering the page content (important part)

The Notion API returns “blocks”, not raw Markdown. To render to the web, you have a few options:

  • Convert blocks → HTML (recommended)
  • Convert blocks → Markdown
  • Hand-roll your own renderer

Popular renderers:

  • notion-to-md (Markdown)
  • notion-client + custom renderer
  • react-notion-x (React)

Code snippet: Convert Notion blocks to Markdown (example)

import { NotionToMarkdown } from "notion-to-md";
 
const n2m = new NotionToMarkdown({ notionClient: notion });
 
export async function getPostMarkdown(pageId) {
	const mdBlocks = await n2m.pageToMarkdown(pageId);
	const mdString = n2m.toMarkdownString(mdBlocks);
	return mdString.parent;
}

Handling images & files

In Notion, images can be:

  • Hosted by Notion (time-limited signed URLs)
  • External URLs

For production sites:

  • Prefer external image URLs, or
  • Download/cache images during build, or
  • Re-fetch frequently if you rely on Notion-hosted URLs

Keeping content in sync

Three common approaches:

  1. Static build (Next.js / Astro): fetch at build time
  2. ISR / incremental rebuilds: revalidate every N minutes
  3. Webhook-style sync: store posts in your own DB and update when Notion changes

If you’re starting out, choose (1) or (2).


Slug rules (quick checklist)

  • lowercase
  • hyphen-separated
  • stable (don’t change after publishing)
  • unique

Example: notion-integration-as-cms-with-code-snippets


Common pitfalls

  • Forgetting to publish (Published checkbox)
  • Changing property names (breaks queries)
  • Missing slug uniqueness
  • Rendering issues (Notion blocks don’t map 1:1 to Markdown)

Conclusion

Notion-as-CMS is a sweet spot: fast authoring, structured metadata, and simple publishing. Start with a minimal database, render blocks safely, and add caching once traffic grows.

If you want, you can extend this setup with:

  • a “Featured” flag
  • canonical URLs
  • SEO fields (meta title/description)
  • an RSS feed