High-fidelity bidirectional Markdown ↔ Notion conversion and synchronization SDK
Project description
notionify
High-fidelity bidirectional Markdown ↔ Notion conversion and synchronization SDK for Python 3.10+.
Features
- Markdown → Notion — Parse Markdown and create Notion pages with headings, lists, code blocks, tables, math equations, images, and more
- Notion → Markdown — Export Notion pages back to clean Markdown
- Diff sync — Update pages incrementally with minimal API calls using LCS-based diffing
- Image pipeline — Automatic upload of local files, data URIs, and external URLs via the Notion file upload API
- Async support — Full async client for high-throughput workflows
- Typed errors — Every failure has a specific error class with actionable context
- Retry & rate limiting — Built-in exponential backoff, jitter, and rate-limit compliance
Installation
pip install notionify
Quick Start
Create a page from Markdown
from notionify import NotionifyClient
with NotionifyClient(token="ntn_xxxxx") as client:
result = client.create_page_with_markdown(
parent_id="<page-or-database-id>",
title="Meeting Notes",
markdown="# Agenda\n\n- Review Q4 goals\n- **Action items**\n\n```python\nprint('hello')\n```",
)
print(result.page_id)
Append content to an existing page
client.append_markdown(
target_id="<page-id>",
markdown="## New Section\n\nAppended content with *formatting*.",
)
Update a page with diff sync
Only the changed blocks are updated — unchanged content is left untouched:
result = client.update_page_from_markdown(
page_id="<page-id>",
markdown=updated_markdown,
strategy="diff", # default; use "overwrite" to replace everything
)
print(f"Kept {result.blocks_kept}, inserted {result.blocks_inserted}, "
f"deleted {result.blocks_deleted}")
Export a Notion page to Markdown
markdown = client.page_to_markdown("<page-id>", recursive=True)
print(markdown)
Async client
import asyncio
from notionify import AsyncNotionifyClient
async def main():
async with AsyncNotionifyClient(token="ntn_xxxxx") as client:
result = await client.create_page_with_markdown(
parent_id="<page-id>",
title="Async Page",
markdown="# Created asynchronously\n\nWith full image support.",
)
print(result.page_id)
asyncio.run(main())
Configuration
All options are passed as keyword arguments to the client constructor:
client = NotionifyClient(
token="ntn_xxxxx",
math_strategy="equation", # "equation" | "code" | "latex_text"
image_upload=True, # auto-upload local/data-URI images
image_fallback="skip", # "skip" | "placeholder" | "raise"
image_base_dir="/safe/path", # restrict local image reads to this dir
enable_tables=True, # convert Markdown tables to Notion tables
retry_max_attempts=5, # max retries on transient failures
rate_limit_rps=3.0, # requests per second limit
timeout_seconds=30.0, # per-request timeout
)
See the API Reference for the full list of options.
Error Handling
All errors inherit from NotionifyError and carry structured context:
from notionify import NotionifyClient, NotionifyRateLimitError, NotionifyError
with NotionifyClient(token="ntn_xxxxx") as client:
try:
client.create_page_with_markdown(
parent_id="<page-id>",
title="Test",
markdown="# Hello",
)
except NotionifyRateLimitError as e:
print(f"Rate limited, retry after: {e.context}")
except NotionifyError as e:
print(f"[{e.code}] {e.message}")
Documentation
- Quickstart — Installation, first page, async usage
- API Reference — Full method signatures, config, result types
- Conversion Matrix — Markdown/Notion compatibility table
- Error Cookbook — Handling rate limits, images, conflicts
- Migration Guide — Upgrading from v1.x/v2.x
- FAQ — Common questions and answers
Development
# Clone and install dev dependencies
git clone https://github.com/notionify/notionify.git
cd notionify
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Lint and type-check
ruff check src/ tests/
mypy src/notionify # strict mode via pyproject.toml
# Performance benchmarks
pytest tests/perf/ -v -s
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file notionify-0.1.2.tar.gz.
File metadata
- Download URL: notionify-0.1.2.tar.gz
- Upload date:
- Size: 405.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d613e49b277735acd62d52a4deb0687b68a221f2305e832c1215236e68c1e12
|
|
| MD5 |
8f50b2e9d0168f070f706c4ff36f348c
|
|
| BLAKE2b-256 |
a9809455d75ba7afb69368a9596f42c9488ec6fd762d8ee0908495179882147d
|
Provenance
The following attestation bundles were made for notionify-0.1.2.tar.gz:
Publisher:
publish.yml on nerdneilsfield/notionify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
notionify-0.1.2.tar.gz -
Subject digest:
5d613e49b277735acd62d52a4deb0687b68a221f2305e832c1215236e68c1e12 - Sigstore transparency entry: 1393347939
- Sigstore integration time:
-
Permalink:
nerdneilsfield/notionify@8acaf16e6aa4c1db92f73ccbb921be0d3902e746 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/nerdneilsfield
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8acaf16e6aa4c1db92f73ccbb921be0d3902e746 -
Trigger Event:
push
-
Statement type:
File details
Details for the file notionify-0.1.2-py3-none-any.whl.
File metadata
- Download URL: notionify-0.1.2-py3-none-any.whl
- Upload date:
- Size: 99.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c81c3eec97d30649a10b9dfd46d73aa3c1db6c310676e544c38179c4103a2959
|
|
| MD5 |
d501a88d0fc1432a66d0979436cff3ae
|
|
| BLAKE2b-256 |
84e2e924abec21d04b7d66de66447e19251a277be3a69ee91c97e11626b0899d
|
Provenance
The following attestation bundles were made for notionify-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on nerdneilsfield/notionify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
notionify-0.1.2-py3-none-any.whl -
Subject digest:
c81c3eec97d30649a10b9dfd46d73aa3c1db6c310676e544c38179c4103a2959 - Sigstore transparency entry: 1393347963
- Sigstore integration time:
-
Permalink:
nerdneilsfield/notionify@8acaf16e6aa4c1db92f73ccbb921be0d3902e746 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/nerdneilsfield
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8acaf16e6aa4c1db92f73ccbb921be0d3902e746 -
Trigger Event:
push
-
Statement type: