Getting Started
This guide shows how to install Notion CMS, configure your first client, and run a simple database query. It assumes you already have a Notion integration token and at least one database.
Install
bash
pnpm add @mikemajara/notion-cms @notionhq/client
Create a NotionCMS instance
typescript
import { NotionCMS } from "@mikemajara/notion-cms"
const notion = new NotionCMS(process.env.NOTION_TOKEN!)
- Provide a Notion integration token with read access to your target databases.
- Optional configuration (file caching, debug logging) is covered in Configuration .
Generating types
The CLI generator produces database-specific metadata that powers autocomplete and runtime validation. You must import from this file in order to query your database. The generator automatically registers your database when generating the types. For simplicity you can define a script in your package.json file which calls the @mikemajara/notion-cms library, as follows.
package.json
json
{
"scripts": {
"notion:generate": "source .env && npx @mikemajara/notion-cms generate -t $NOTION_API_KEY -d $NOTION_CMS_DATABASE_ID"
}
}
This will create the generated types by default in the directory /notion/notion-types-<database_name>.ts file, so you can start querying your database with strong types and intellisense.
Run your first query
The query method makes it easy to query, filter and paginate with a familiar syntax.
typescript
const posts = await notion
.query("blog")
.filter("Status", "equals", "Published")
.sort("PublishedAt", "descending")
.limit(5)
console.log(posts)
Fetching data by default fetches the Raw Notion response as defined by them.
- The default layer is
raw; the response follows the same structure as the Notion API return value. - Switch to
advancedorsimplelayers using.query("blog", { recordType: "advanced" })or.query("blog", { recordType: "simple" }).
[!TIP] Most of the time you will want a simple record to be returned. Overall the design decision is you start complex, but have an easy API to convert back and forth between the different layers of complexity of a response. If your whole application will be using one single database, and you will only ever use the simple record, you can easily create a wrapper for it in a local file mapping query to a simple query for better DX.
typescript
const querySimple = (db: string) => query(db, { recordType: "simple" })
[!NOTE] Caching is the only thing that works by default when querying any kind of block in Notion. This is configured when instantiating the NotionCMS, and will automatically change the references to files and images in your raw responses.
Fetch page content
You can fetch the page content with getPageContent. As usual, the default layer for the returned records is the raw response from notion, which you can conveniently change and post-process as needed.
typescript
const record = await notionCMS.getRecord(pageId)
// Turn the record into a simple instance.
const recordSimple = await convertRecordToSimple(record)
// Fetch the page content blocks
const pageContentRaw = await notionCMS.getPageContent(pageId)
// Convert to a familiar format
const pageContentMarkdown = await blocksToMarkdown(pageContentRaw)
const pageContentHtml = blocksToHtml(pageContentRaw)
By default, the page content is fetched recursively, which means, when fetching the content of your page, you don't need to worry about nested lists, or toggles.
Markdown conversion happens following the Markdown standard.
Process the blocks with the helpers documented in content-blocks.md.
Check supported blocks in support.md
Next steps
- Configure file handling and the code generator in
configuration.md. - Explore query patterns in
database-queries.md. - Learn about block conversion and rendering in
content-blocks.md.
Last updated: