
[!IMPORTANT]
Upgrading from v1.4?
Version 2.0 is a full redesign with a new API — NotionClient replaces the old Page, Database, and Search classes.
v1.4.x will continue to receive security fixes only. See the migration table and CHANGELOG for details.
notion-database
Python client for the Notion API — easy to use, 1-to-1 API mapping, AI/MCP friendly
Notion API version supported: 2026-03-11
What's new in 2.0
client.pages.retrieve_markdown() | Retrieve a page as enhanced Markdown (GET /pages/{id}/markdown) |
client.pages.update_markdown() | Replace page content with Markdown (PATCH /pages/{id}/markdown) |
client.pages.append_markdown() | Append Markdown to the bottom of a page (PATCH /pages/{id}/markdown) |
client.pages.create(timezone=...) | IANA timezone for template variables (@now, @today) |
client.blocks.append_children(position=...) | Insert blocks at start, end, or after_block |
client.databases.query(in_trash=...) | Filter trashed / non-trashed rows |
client.databases.update(is_inline=..., in_trash=..., is_locked=...) | Toggle inline layout, trash, and lock state |
client.databases.create(initial_data_source=...) | Pre-populate a database from a data source on creation |
PropertySchema.button() | Automation button column |
PropertySchema.location() | Geographic location column |
PropertySchema.last_visited_time() | Last visited time column (read-only) |
PropertySchema.rollup(relation_property_id=..., rollup_property_id=...) | ID-based rollup lookup params |
PropertySchema.verification() | Wiki page verification column |
PropertyValue.verification() | Set wiki page verification state |
BlockContent.tab() / tab_group() | Tab layout blocks |
Filter.created_by() / last_edited_by() | Filter by creator or last editor |
Filter.formula(name, value_type) | Filter on formula property results |
Filter.rollup(name, aggregate, value_type) | Filter on rollup aggregates |
Filter.verification() | Filter on wiki verification state |
Install
pip install notion-database==2.0.0
Quick start
from notion_database import (
NotionClient,
PropertyValue,
PropertySchema,
BlockContent,
RichText,
Filter,
Sort,
Icon,
Cover,
)
client = NotionClient("secret_xxx")
Databases
db = client.databases.retrieve("database-id")
db = client.databases.create(
parent={"type": "page_id", "page_id": "page-id"},
title=[RichText.text("My Database")],
properties={
"Name": PropertySchema.title(),
"Status": PropertySchema.select([
{"name": "Active", "color": "green"},
{"name": "Done", "color": "gray"},
]),
"Score": PropertySchema.number("number"),
"Due": PropertySchema.date(),
},
)
results = client.databases.query(
"database-id",
filter=Filter.select("Status").equals("Active"),
sorts=[Sort.descending("Score")],
)
pages = results["results"]
results = client.databases.query(
"database-id",
filter=Filter.and_([
Filter.select("Status").equals("Active"),
Filter.number("Score").greater_than(80),
]),
)
all_pages = client.databases.query_all("database-id")
client.databases.update(
"database-id",
title=[RichText.text("Renamed DB")],
is_inline=False,
is_locked=True,
)
Pages
page = client.pages.create(
parent={"database_id": "database-id"},
properties={
"Name": PropertyValue.title("Hello, Notion 2.0!"),
"Status": PropertyValue.select("Active"),
"Score": PropertyValue.number(95),
"Due": PropertyValue.date("2024-12-31"),
"Done": PropertyValue.checkbox(False),
},
icon=Icon.emoji("🚀"),
cover=Cover.external("https://example.com/cover.jpg"),
children=[
BlockContent.heading_1("Introduction"),
BlockContent.paragraph("This page was created via notion-database 2.0."),
],
)
page = client.pages.retrieve("page-id")
client.pages.update(
"page-id",
properties={
"Status": PropertyValue.select("Done"),
"Done": PropertyValue.checkbox(True),
},
)
client.pages.archive("page-id")
client.pages.archive("page-id", archived=False)
Blocks
client.blocks.append_children(
"page-id",
children=[
BlockContent.heading_2("Section"),
BlockContent.paragraph([
RichText.text("Normal text, "),
RichText.text("bold", bold=True),
RichText.text(", and "),
RichText.text("italic", italic=True),
]),
BlockContent.bulleted_list_item("First item"),
BlockContent.bulleted_list_item("Second item"),
BlockContent.to_do("Finish docs", checked=False),
BlockContent.code("print('hello')", language="python"),
BlockContent.divider(),
BlockContent.image("https://example.com/image.png", caption="Fig 1"),
BlockContent.column_list([
[BlockContent.paragraph("Left column")],
[BlockContent.paragraph("Right column")],
]),
BlockContent.tab_group([
BlockContent.tab("Overview", [BlockContent.paragraph("Overview content")]),
BlockContent.tab("Details", [BlockContent.paragraph("Details content")]),
]),
],
)
client.blocks.append_children("page-id", children=[
BlockContent.paragraph("Prepended to top"),
], position={"type": "start"})
client.blocks.append_children("page-id", children=[
BlockContent.paragraph("After a specific block"),
], position={"type": "after_block", "after_block": {"id": "block-id"}})
response = client.blocks.retrieve_children("page-id")
blocks = response["results"]
all_blocks = client.blocks.retrieve_all_children("page-id")
client.blocks.delete("block-id")
Search
results = client.search.search("Project")
db_results = client.search.search_databases("Project")
page_results = client.search.search_pages("Meeting notes")
all_results = client.search.search_all("Q1")
Users
me = client.users.me()
all_users = client.users.list_all()
user = client.users.retrieve("user-id")
comments = client.comments.retrieve("page-id")
client.comments.create(
parent={"page_id": "page-id"},
rich_text=[RichText.text("Great work!")],
)
Filters reference
Filter.text("Name").equals("Alice")
Filter.text("Name").contains("Al")
Filter.text("Name").starts_with("A")
Filter.text("Name").is_empty()
Filter.number("Score").greater_than(80)
Filter.number("Score").less_than_or_equal_to(100)
Filter.checkbox("Done").equals(True)
Filter.select("Status").equals("Active")
Filter.status("Status").does_not_equal("Archived")
Filter.multi_select("Tags").contains("python")
Filter.date("Due").before("2025-01-01")
Filter.date("Due").past_week()
Filter.date("Due").next_month()
Filter.people("Assignee").contains("user-id")
Filter.created_by("Created By").contains("user-id")
Filter.last_edited_by("Last Edited By").does_not_contain("user-id")
Filter.created_time().after("2024-01-01")
Filter.last_edited_time().past_week()
Filter.formula("Computed", "string").equals("ok")
Filter.formula("Score", "number").greater_than(50)
Filter.rollup("Tasks", "any", "number").greater_than(0)
Filter.rollup("Tags", "every", "rich_text").contains("urgent")
Filter.verification("Verified").equals("verified")
Filter.and_([
Filter.select("Status").equals("Active"),
Filter.number("Score").greater_than(80),
])
Filter.or_([
Filter.text("Name").contains("Alice"),
Filter.text("Name").contains("Bob"),
])
Filter.and_([
Filter.checkbox("Done").equals(False),
Filter.or_([
Filter.select("Priority").equals("High"),
Filter.date("Due").before("2025-01-01"),
]),
])
Filter.raw({"property": "Formula", "formula": {"string": {"equals": "ok"}}})
Sorts reference
Sort.by_property("Name")
Sort.by_property("Score", "descending")
Sort.ascending("Name")
Sort.descending("CreatedAt")
Sort.by_timestamp("created_time", "descending")
Sort.by_timestamp("last_edited_time")
RichText reference
RichText.text("plain")
RichText.text("bold", bold=True)
RichText.text("italic", italic=True)
RichText.text("underline", underline=True)
RichText.text("strike", strikethrough=True)
RichText.text("code", code=True)
RichText.text("colored", color="red")
RichText.text("link", link="https://example.com")
RichText.mention_page("page-id")
RichText.mention_database("db-id")
RichText.mention_user("user-id")
RichText.mention_date("2024-01-01", end="2024-01-31")
RichText.equation("E=mc^2")
PropertySchema reference
from notion_database import PropertySchema
{
"Name": PropertySchema.title(),
"Notes": PropertySchema.rich_text(),
"Website": PropertySchema.url(),
"Email": PropertySchema.email(),
"Phone": PropertySchema.phone_number(),
"Score": PropertySchema.number("number"),
"Price": PropertySchema.number("dollar"),
"Category": PropertySchema.select([{"name": "A", "color": "green"}]),
"Tags": PropertySchema.multi_select(),
"Status": PropertySchema.status(),
"Due": PropertySchema.date(),
"Created": PropertySchema.created_time(),
"CreatedBy": PropertySchema.created_by(),
"LastEdited": PropertySchema.last_edited_time(),
"LastEditedBy": PropertySchema.last_edited_by(),
"Done": PropertySchema.checkbox(),
"Files": PropertySchema.files(),
"People": PropertySchema.people(),
"Action": PropertySchema.button(),
"Location": PropertySchema.location(),
"LastVisited": PropertySchema.last_visited_time(),
"Formula": PropertySchema.formula("prop('Score') * 2"),
"UniqueID": PropertySchema.unique_id(prefix="ITEM"),
"Related": PropertySchema.relation("other-database-id"),
"Rollup": PropertySchema.rollup("Related", "Count", "count"),
"RollupByID": PropertySchema.rollup(
"Related", "Count", "sum",
relation_property_id="rel-id",
rollup_property_id="prop-id",
),
"Verified": PropertySchema.verification(),
}
Error handling
from notion_database import (
NotionAPIError,
NotionNotFoundError,
NotionRateLimitError,
NotionUnauthorizedError,
)
import time
try:
page = client.pages.retrieve("invalid-id")
except NotionNotFoundError:
print("Page not found")
except NotionRateLimitError:
time.sleep(1)
page = client.pages.retrieve("invalid-id")
except NotionUnauthorizedError:
print("Check your integration token")
except NotionAPIError as e:
print(f"[{e.status_code}] {e.code}: {e.message}")
Color constants
from notion_database.const import (
DEFAULT, GRAY, BROWN, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK, RED,
GRAY_BACKGROUND, BROWN_BACKGROUND, ORANGE_BACKGROUND, YELLOW_BACKGROUND,
GREEN_BACKGROUND, BLUE_BACKGROUND, PURPLE_BACKGROUND, PINK_BACKGROUND,
RED_BACKGROUND,
)
BlockContent.paragraph("Highlighted", color=RED_BACKGROUND)
MCP integration
NotionClient is designed to map directly to Notion API endpoints, making it
straightforward to expose as MCP tools. Each sub-client (databases,
pages, blocks, search, users, comments) corresponds to one section
of the Notion API docs. All parameters are typed and documented, so an AI can
introspect the signatures without additional context.
License
LGPLv3