DEV Community

Alex Spinov
Alex Spinov

Posted on

Notion Has a Free API — Here's How to Build a CMS, Database, or Automation on Top of It

A content team I know was using a custom WordPress setup for their editorial calendar. 3 plugins, $50/month hosting, constant maintenance. They moved everything to a Notion database with the API powering their frontend. Cost: $0. Setup time: 2 hours.

What Notion's API Offers for Free

Notion free plan + API:

  • Unlimited pages and blocks
  • Full REST API — CRUD for pages, databases, blocks, users
  • Database queries with filters, sorts, and pagination
  • Search across the entire workspace
  • OAuth for building integrations
  • Webhooks (via polling or third-party services)

Quick Start

# Create an integration at notion.so/my-integrations
# Get your Internal Integration Token
export NOTION_TOKEN='ntn_YOUR_TOKEN'

# Share a database with your integration (click '...' > 'Add connections')
Enter fullscreen mode Exit fullscreen mode

Query a Database

const { Client } = require('@notionhq/client');

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

// Query database with filters
const response = await notion.databases.query({
  database_id: 'DATABASE_ID',
  filter: {
    and: [
      { property: 'Status', select: { equals: 'Published' } },
      { property: 'Category', multi_select: { contains: 'Tutorial' } }
    ]
  },
  sorts: [{ property: 'Date', direction: 'descending' }],
  page_size: 10
});

const articles = response.results.map(page => ({
  id: page.id,
  title: page.properties.Name.title[0]?.plain_text,
  status: page.properties.Status.select?.name,
  date: page.properties.Date.date?.start,
  tags: page.properties.Tags.multi_select.map(t => t.name)
}));
Enter fullscreen mode Exit fullscreen mode

Create Pages

// Add a new item to a database
const newPage = await notion.pages.create({
  parent: { database_id: 'DATABASE_ID' },
  properties: {
    Name: { title: [{ text: { content: 'New Blog Post' } }] },
    Status: { select: { name: 'Draft' } },
    Author: { people: [{ id: 'USER_ID' }] },
    Date: { date: { start: '2026-03-29' } },
    Tags: { multi_select: [{ name: 'JavaScript' }, { name: 'Tutorial' }] }
  },
  children: [
    {
      object: 'block',
      type: 'heading_2',
      heading_2: { rich_text: [{ text: { content: 'Introduction' } }] }
    },
    {
      object: 'block',
      type: 'paragraph',
      paragraph: { rich_text: [{ text: { content: 'Start writing here...' } }] }
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

Use Notion as a CMS

// Fetch all published articles for your blog
async function getPublishedArticles() {
  const { results } = await notion.databases.query({
    database_id: process.env.NOTION_BLOG_DB,
    filter: { property: 'Published', checkbox: { equals: true } },
    sorts: [{ property: 'Date', direction: 'descending' }]
  });

  return Promise.all(results.map(async (page) => {
    // Get page content (blocks)
    const blocks = await notion.blocks.children.list({ block_id: page.id });

    return {
      slug: page.properties.Slug.rich_text[0]?.plain_text,
      title: page.properties.Name.title[0]?.plain_text,
      date: page.properties.Date.date?.start,
      content: blocks.results // Convert blocks to HTML/markdown
    };
  }));
}
Enter fullscreen mode Exit fullscreen mode

Automate with Notion

// Daily standup bot — create today's entry
async function createStandupEntry(userId, yesterday, today, blockers) {
  await notion.pages.create({
    parent: { database_id: 'STANDUP_DB_ID' },
    properties: {
      Date: { date: { start: new Date().toISOString().split('T')[0] } },
      Person: { people: [{ id: userId }] },
      Yesterday: { rich_text: [{ text: { content: yesterday } }] },
      Today: { rich_text: [{ text: { content: today } }] },
      Blockers: { rich_text: [{ text: { content: blockers || 'None' } }] }
    }
  });
}

// Weekly report generator
async function generateWeeklyReport(teamDbId) {
  const oneWeekAgo = new Date(Date.now() - 7 * 86400000).toISOString();

  const { results } = await notion.databases.query({
    database_id: teamDbId,
    filter: {
      and: [
        { property: 'Status', select: { equals: 'Done' } },
        { property: 'Completed', date: { after: oneWeekAgo } }
      ]
    }
  });

  return results.map(p => p.properties.Name.title[0]?.plain_text);
}
Enter fullscreen mode Exit fullscreen mode

Popular Use Cases

  • Blog CMS: Notion database → API → Next.js/Astro frontend
  • Job board: Notion database for listings, API for the website
  • Internal tools: CRM, project tracker, knowledge base
  • Automation: Slack bot creates Notion pages, email triggers database updates

Need to scrape data into Notion? Check out my web scraping actors on Apify — collect data from any website.

Need a custom Notion integration? Email me at spinov001@gmail.com.

Top comments (0)