Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Notion

To integrate Replit with Notion, create an internal integration in Notion's developer settings, share your database or page with it, store the integration token in Replit Secrets (lock icon 🔒), and use the Notion REST API to read and write databases, pages, and blocks from your Python or Node.js server.

What you'll learn

  • How to create a Notion internal integration and obtain an API token
  • How to share Notion databases and pages with your integration
  • How to query and filter Notion databases from your Replit backend
  • How to create and update pages with rich block content
  • How to use the official Notion SDK versus direct HTTP calls
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read20 minutesProductivityMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Notion, create an internal integration in Notion's developer settings, share your database or page with it, store the integration token in Replit Secrets (lock icon 🔒), and use the Notion REST API to read and write databases, pages, and blocks from your Python or Node.js server.

Why Integrate Notion with Replit?

Notion has become a central knowledge base for thousands of teams — storing documentation, project trackers, CRM pipelines, content calendars, and more. When you connect Replit to Notion's API, you can build automations that push data into Notion from external sources, sync Notion database entries with other systems, generate reports automatically, or build custom interfaces on top of Notion data.

Notion's API treats pages as documents and databases as structured collections of pages. Each database has a schema — properties like text, numbers, dates, selects, and relations — that define the structure of every entry. Through the API, you can query databases with filters and sorts (just like a SQL SELECT), create new database entries, update existing ones, and read the block content that makes up any page. The @notionhq/client SDK for Node.js and the unofficial notion-client Python package wrap these operations with typed methods and built-in retry logic.

Internal integrations are the simplest Notion auth approach for Replit projects: you create an integration in Notion's developer portal, get a token, and share specific pages or databases with that integration. Unlike OAuth flows that require user login, internal integrations are scoped to your Notion workspace and work server-to-server — perfect for automated backends that sync data without human interaction. This makes them an ideal fit for Replit apps acting as automation backends or data bridges.

Integration method

Standard API Integration

Replit connects to Notion via the Notion REST API using an internal integration token stored in Replit Secrets. Your server-side Express or Flask app authenticates with this token and makes API calls to query databases, create pages, and read or update block content. Notion databases must be explicitly shared with your integration before your code can access them.

Prerequisites

  • A Notion account (free tier is sufficient for API access)
  • A Notion workspace with a database or page you want to access from Replit
  • A Replit account with a Node.js or Python Repl created
  • Basic knowledge of REST APIs and JSON data structures

Step-by-step guide

1

Create a Notion internal integration

Go to notion.so/my-integrations (or navigate to Settings > Integrations in Notion, then click 'Develop your own integrations'). Click 'New integration'. Give it a name like 'Replit App' and select the Workspace it should belong to. Set the integration type to 'Internal' — this is correct for server-side Replit apps that access your own Notion workspace. Under Capabilities, choose what the integration can do: Read content (to query databases and read pages), Update content (to modify existing pages), Insert content (to create new pages). Only enable the capabilities your app actually needs. Click 'Submit'. Notion will display your Internal Integration Secret — a token starting with 'secret_'. Copy this token immediately. Notion integration tokens don't expire, but they should be treated as sensitive credentials — anyone with this token has the access level you configured. If the token is ever compromised, you can generate a new one from the same integration settings page and update your Replit Secret.

Pro tip: Notion integration tokens do not expire, but you can regenerate them at any time from notion.so/my-integrations if you suspect a token has been compromised.

Expected result: A Notion internal integration is created with appropriate capabilities and you have the integration token (starting with 'secret_') copied.

2

Share your Notion database with the integration

This is the most commonly missed step when setting up a Notion integration. By default, your integration has no access to any pages or databases — you must explicitly grant access. Open the Notion database or page you want your Replit app to access. Click the three-dot menu (...) in the top right corner, or click 'Share' if visible. In the share dialog, look for 'Connections' (in newer Notion UI) or 'Invite' (in older UI). Search for your integration name and click it to grant access. The integration now has access to this database and all pages within it. You also need the database ID, which you can find in the database's URL when you open it in a browser: it's the 32-character string in the URL path after the workspace name and before any query parameters. For example, in https://notion.so/myworkspace/abc123def456...?v=..., the database ID is abc123def456... — format it with hyphens as a UUID: abc123de-f456-...-... for the API calls (the SDK handles this automatically if you pass the raw ID).

extract_id.py
1# Python extract Notion database ID from URL
2notion_url = "https://www.notion.so/myworkspace/abc123def4561234abcd1234567890ab?v=..."
3# The database ID is the 32-char hex string before '?'
4path = notion_url.split('?')[0].split('/')[-1]
5# Remove hyphens if present, then reformat
6raw_id = path.replace('-', '')
7if len(raw_id) == 32:
8 formatted_id = f"{raw_id[:8]}-{raw_id[8:12]}-{raw_id[12:16]}-{raw_id[16:20]}-{raw_id[20:]}"
9 print(f"Database ID: {formatted_id}")
10else:
11 print(f"URL format unexpected, raw path: {path}")

Pro tip: If your API calls return a 404 'Could not find database' error even with the correct ID, it means the integration hasn't been shared with the database. Go back to the database in Notion and share it with your integration via the Connections menu.

Expected result: The integration appears in the database's Connections/Share settings. The database ID is extracted from the URL and formatted correctly.

3

Store the Notion token and configure secrets

Open your Repl and click the lock icon (🔒) in the left sidebar to open the Secrets pane. Add NOTION_TOKEN with your integration secret (starting with 'secret_') as the value. Also add NOTION_DATABASE_ID with your database's UUID-formatted ID. Having the database ID as a secret makes it easy to switch between different Notion databases for staging and production without code changes. Replit Secrets are AES-256 encrypted and injected as environment variables — they are never stored in code or visible in Git history. After adding the secrets, verify they are accessible in your Repl by running a quick check script. Install the Notion SDK — in Node.js run npm install @notionhq/client, in Python add notion-client to requirements.txt or install via the Packages pane.

check.js
1// Node.js — verify Notion secrets (check.js)
2const requiredSecrets = ['NOTION_TOKEN', 'NOTION_DATABASE_ID'];
3for (const key of requiredSecrets) {
4 const val = process.env[key];
5 if (!val) {
6 throw new Error(`${key} not found. Add it in Replit Secrets (lock icon 🔒).`);
7 }
8 console.log(`${key}: loaded (${val.length} chars)`);
9}
10console.log('All Notion secrets verified.');

Expected result: Both NOTION_TOKEN and NOTION_DATABASE_ID appear in Replit Secrets. The verification script confirms both are loaded without errors.

4

Query a Notion database

The Notion API's database query endpoint lets you retrieve pages from a database with optional filters and sorting. Using the official @notionhq/client SDK, call client.databases.query() with your database ID and optional filter and sorts parameters. Filters follow a structured format: each filter specifies a property name, the property type (rich_text, number, date, select, etc.), and the filter condition (equals, contains, after, etc.). Multiple filters can be combined with and/or logic. The query returns a results array of Notion page objects. Each page contains an id, a properties object with typed property values, and a url. Property values are deeply nested — a text property value is at properties.Name.title[0].plain_text, while a select value is at properties.Status.select.name. The SDK includes TypeScript types if you're using TypeScript, making it easier to navigate these nested structures. For plain Node.js, log the full response of a few pages to understand your specific database's property structure before building display logic.

notion.js
1// Node.js — Query Notion database (notion.js)
2const { Client } = require('@notionhq/client');
3
4const notion = new Client({ auth: process.env.NOTION_TOKEN });
5const DATABASE_ID = process.env.NOTION_DATABASE_ID;
6
7async function queryDatabase({ filter = null, sorts = null, pageSize = 100 } = {}) {
8 const params = { database_id: DATABASE_ID, page_size: pageSize };
9 if (filter) params.filter = filter;
10 if (sorts) params.sorts = sorts;
11
12 const response = await notion.databases.query(params);
13 return response.results;
14}
15
16// Helper to extract plain text from Notion title property
17function getTitle(page) {
18 const titleProp = Object.values(page.properties)
19 .find(p => p.type === 'title');
20 return titleProp?.title[0]?.plain_text || 'Untitled';
21}
22
23// Example: get all pages with Status = 'Published'
24async function getPublishedPages() {
25 return queryDatabase({
26 filter: {
27 property: 'Status',
28 select: { equals: 'Published' }
29 },
30 sorts: [{ property: 'Published Date', direction: 'descending' }]
31 });
32}
33
34module.exports = { notion, queryDatabase, getTitle, DATABASE_ID };

Pro tip: Notion database queries return a maximum of 100 results per page. Use the has_more and next_cursor fields in the response to paginate through large databases.

Expected result: queryDatabase() returns an array of Notion page objects from your database. getPublishedPages() returns only pages with the matching status property.

5

Create and update Notion pages

Creating a new database entry uses client.pages.create() with the database as the parent and a properties object that matches your database schema. Each property value must be formatted according to its Notion property type — text values use the rich_text type with an array of text objects, selects use the select type with a name string, dates use the date type with a start string in ISO 8601 format, numbers use the number type directly. Getting this structure right is the trickiest part of the Notion API. The best approach is to first query an existing page in your database and log its properties — the structure of existing values shows you exactly how to format new values. For updating existing pages, use client.pages.update() with the page ID and only the properties you want to change. You can also add block content to a page using client.blocks.children.append() to add paragraphs, headings, lists, code blocks, and more.

crud.js
1// Node.js — Create and update Notion pages (crud.js)
2const { notion, DATABASE_ID } = require('./notion');
3
4async function createPage({ title, status, publishDate, tags = [] }) {
5 const page = await notion.pages.create({
6 parent: { database_id: DATABASE_ID },
7 properties: {
8 // Title property (adjust 'Name' to match your database's title column)
9 Name: {
10 title: [{ text: { content: title } }]
11 },
12 // Select property
13 Status: {
14 select: { name: status }
15 },
16 // Date property
17 'Publish Date': {
18 date: publishDate ? { start: publishDate } : null
19 },
20 // Multi-select property
21 Tags: {
22 multi_select: tags.map(tag => ({ name: tag }))
23 }
24 }
25 });
26 return page;
27}
28
29async function updatePage(pageId, updates) {
30 return notion.pages.update({
31 page_id: pageId,
32 properties: updates
33 });
34}
35
36async function addContentBlock(pageId, text) {
37 return notion.blocks.children.append({
38 block_id: pageId,
39 children: [{
40 object: 'block',
41 type: 'paragraph',
42 paragraph: {
43 rich_text: [{ type: 'text', text: { content: text } }]
44 }
45 }]
46 });
47}
48
49module.exports = { createPage, updatePage, addContentBlock };

Pro tip: Property names in the API are case-sensitive and must exactly match the column names in your Notion database. A mismatch silently creates the page without those properties.

Expected result: createPage() creates a new entry in your Notion database with the specified properties. updatePage() modifies an existing page's properties. addContentBlock() appends a paragraph to a page.

6

Python alternative: Flask with notion-client

The Python notion-client package (pip install notion-client) provides the same interface as the Node.js SDK. Install it via the Packages pane or add to requirements.txt. The Python client uses snake_case method names: client.databases.query(), client.pages.create(), client.blocks.children.append(). Property value structures are identical to Node.js — the same nested dictionaries apply. For querying, pass filter and sorts as Python dictionaries. The pagination cursor works the same way: check has_more and pass start_cursor to the next query call to retrieve subsequent pages. If you prefer to call the Notion API without the SDK, use the requests library with Authorization: Bearer {NOTION_TOKEN} and a Notion-Version: 2022-06-28 header on all requests — the version header is required by the API.

app.py
1# Python Flask Notion integration (app.py)
2import os
3from flask import Flask, request, jsonify
4from notion_client import Client
5
6app = Flask(__name__)
7
8notion = Client(auth=os.environ["NOTION_TOKEN"])
9DATABASE_ID = os.environ["NOTION_DATABASE_ID"]
10
11@app.route("/database", methods=["GET"])
12def list_entries():
13 filter_status = request.args.get("status")
14 params = {"database_id": DATABASE_ID, "page_size": 50}
15 if filter_status:
16 params["filter"] = {
17 "property": "Status",
18 "select": {"equals": filter_status}
19 }
20 response = notion.databases.query(**params)
21 results = []
22 for page in response["results"]:
23 title_prop = next(
24 (p for p in page["properties"].values() if p["type"] == "title"), None
25 )
26 title = title_prop["title"][0]["plain_text"] if title_prop and title_prop["title"] else "Untitled"
27 results.append({"id": page["id"], "title": title, "url": page["url"]})
28 return jsonify(results)
29
30@app.route("/pages", methods=["POST"])
31def create_page():
32 data = request.get_json()
33 page = notion.pages.create(
34 parent={"database_id": DATABASE_ID},
35 properties={
36 "Name": {"title": [{"text": {"content": data["title"]}}]},
37 "Status": {"select": {"name": data.get("status", "Draft")}}
38 }
39 )
40 return jsonify({"id": page["id"], "url": page["url"]})
41
42if __name__ == "__main__":
43 app.run(host="0.0.0.0", port=3000)

Expected result: Flask app queries the Notion database via GET /database and creates new pages via POST /pages.

Common use cases

Automated Content Calendar Sync

When your app publishes a blog post or schedules a social media update, automatically create an entry in a Notion content calendar database with the publication date, status, channel, and link. This keeps your Notion workspace current without manual data entry.

Replit Prompt

Build an Express endpoint that accepts content metadata (title, publish_date, channel, url, status) via POST and creates a new entry in a Notion database with those properties. Store the NOTION_TOKEN and NOTION_DATABASE_ID in Replit Secrets.

Copy this prompt to try it in Replit

CRM Lead Tracker to Notion

When a new lead is captured through your app's signup form or imported from a CSV, create a corresponding Notion page in a CRM database with contact details, lead source, and next action date. Sales teams who live in Notion can then manage their pipeline without switching tools.

Replit Prompt

Build a Flask API that receives lead data from a form submission, validates the required fields, and creates a new Notion database page with Name, Email, Company, Lead Source (select), and Follow-Up Date (date) properties.

Copy this prompt to try it in Replit

Knowledge Base Query API

Expose your Notion knowledge base as a searchable API. When users query your app for information, your Replit backend queries the Notion database for matching pages, retrieves their block content, and returns formatted answers. This is a common pattern for AI-powered documentation chat apps that use Notion as the knowledge source.

Replit Prompt

Build an Express server that queries a Notion database by title search, fetches the block content of matching pages, and returns the combined text as a knowledge base API that a frontend chat app can use to answer user questions.

Copy this prompt to try it in Replit

Troubleshooting

APIResponseError: Could not find database with ID {id}

Cause: Either the database ID is wrong, or the integration has not been shared with this specific database in Notion.

Solution: Open the database in Notion, click the '...' menu or 'Share', find 'Connections', and share the database with your integration. Also verify the database ID in Replit Secrets — copy it fresh from the URL and format it correctly as a UUID. The raw ID from the URL is 32 hex characters without hyphens; the API accepts either format.

typescript
1// Log the database ID being used to verify it
2console.log('Database ID:', process.env.NOTION_DATABASE_ID);
3// Check it's 32 chars (no hyphens) or 36 chars (with hyphens UUID)

APIResponseError: Unauthorized — Invalid API token

Cause: The NOTION_TOKEN secret is incorrect, has extra whitespace from copy-pasting, or the integration was deleted or regenerated in Notion.

Solution: Go to notion.so/my-integrations and verify your integration exists and is active. Click on the integration to view or regenerate the token. In Replit Secrets, delete NOTION_TOKEN and re-add it by pasting the token carefully. Ensure there are no leading or trailing spaces — Replit's Secrets editor can sometimes include whitespace from pasting.

typescript
1// Check token format — should start with 'secret_'
2console.log('Token prefix:', process.env.NOTION_TOKEN?.substring(0, 8));

Properties are missing or null when creating pages — no error is thrown

Cause: Property names in the API call don't exactly match the column names in your Notion database, or the property value is formatted incorrectly for the property type.

Solution: Query an existing page from the database and log its full properties object — this shows you exactly what each property is named and how values are structured. Property names are case-sensitive. Compare your create payload structure against what the query returns. Common mistakes: using 'text' instead of 'rich_text' for the type field inside a title property, or using a string directly for select instead of { name: 'value' }.

typescript
1// Log existing page properties to see correct structure
2const pages = await notion.databases.query({ database_id: DATABASE_ID, page_size: 1 });
3if (pages.results.length > 0) {
4 console.log(JSON.stringify(pages.results[0].properties, null, 2));
5}

Pagination — only getting first 100 results from a large database

Cause: The Notion API returns a maximum of 100 results per request. The has_more field is true when there are additional results, but your code isn't following the cursor.

Solution: Check the has_more field in the query response. If true, pass the next_cursor value as start_cursor in the next query call. Repeat until has_more is false. For large databases, implement this as a recursive or loop-based pagination function.

typescript
1async function getAllPages(databaseId) {
2 let results = [], cursor = undefined;
3 do {
4 const resp = await notion.databases.query({
5 database_id: databaseId,
6 start_cursor: cursor,
7 page_size: 100
8 });
9 results.push(...resp.results);
10 cursor = resp.has_more ? resp.next_cursor : undefined;
11 } while (cursor);
12 return results;
13}

Best practices

  • Store NOTION_TOKEN and NOTION_DATABASE_ID in Replit Secrets (lock icon 🔒) — never hardcode tokens in source files
  • Always share databases explicitly with your integration before running code — missing this step causes confusing 'database not found' errors
  • Use the minimum required capabilities when creating the integration: only enable read, insert, or update based on what your app actually needs
  • Query an existing page first to understand your database's property structure — property values are deeply nested and structure varies by type
  • Implement pagination for database queries — the API returns a maximum of 100 results per call and has_more signals when more are available
  • Use the official @notionhq/client SDK (Node.js) or notion-client (Python) rather than raw HTTP calls — they handle retries, rate limiting, and type safety
  • Cache database schema metadata (property names and types) rather than fetching it on every request — the schema rarely changes and the extra API call adds latency
  • Deploy on Autoscale for Notion-backed apps — Notion's API is not latency-sensitive enough to require Reserved VM, and Autoscale handles the typical bursty usage pattern

Alternatives

Frequently asked questions

How do I connect Replit to Notion?

Create an internal integration at notion.so/my-integrations, copy the integration token, and store it as NOTION_TOKEN in Replit Secrets (lock icon 🔒 in sidebar). Then share the specific database you want to access with the integration by using the Connections menu in Notion. Your server reads the token via process.env.NOTION_TOKEN (Node.js) or os.environ['NOTION_TOKEN'] (Python).

Why does the Notion API keep returning 'Could not find database'?

This almost always means you forgot to share the database with your integration. Open the database in Notion, click the '...' menu or Share button, find Connections, and add your integration. The database must be shared before your integration token can access it — this is a deliberate Notion security design.

Can I use the Notion API in Replit for free?

Yes — Notion's API is free on all plans including the free personal plan. There is no charge for API calls. Replit's free tier can run the integration in development. For production apps, you'll need a Replit deployment.

How do I get a Notion database ID?

Open the database in Notion in your browser. The URL contains the database ID as a 32-character hexadecimal string in the path. For example, in https://notion.so/workspace/abc123def456...?v=..., the ID is abc123def456... — copy everything between the last slash and the question mark. You can use this raw ID or format it with hyphens as a UUID — the Notion SDK accepts both formats.

What is the difference between Notion internal and public integrations?

Internal integrations access your own Notion workspace with a fixed token — ideal for Replit automation backends. Public integrations use OAuth 2.0 and can be installed by any Notion user, making them suitable for marketplace apps. For personal or company automations in Replit, internal integrations are simpler and appropriate.

How do I handle Notion API rate limits in Replit?

Notion's API has a rate limit of approximately 3 requests per second per integration token. The official Notion SDK handles rate limit retries automatically when you receive a 429 response. For bulk operations that exceed this limit, add delays between requests or use a queue to process items sequentially with a small pause between each call.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.