A high-performance Rust CLI tool that converts Notion pages and databases into structured prompts for AI models. Transform your Notion content into perfectly formatted AI inputs with recursive fetching, parallel processing, and smart child database embedding.
Quick Demo:
notion2prompt https://notion.so/your-page→ AI-ready structured prompt in seconds
Tired of manually copying and formatting Notion content for AI tools? This CLI automates the entire process, preserving structure, handling complex databases, and generating prompts optimized for AI models like Claude and GPT.
- ⚡ High Performance: Parallel fetching with work-stealing queues and priority-based scheduling
- 🗃️ Complete Database Support: Automatically embeds child databases within parent pages
- =� Recursive Fetching: Configurable depth limits with cycle detection
- 📝 Multiple Output Formats: Markdown with customizable Handlebars templates
- 📋 Flexible Output: File, clipboard, or pipe output options
- 🔒� Type Safety: Strong typing with domain-specific newtypes
- 🚀 Smart Caching: String interning and object pooling for efficiency
- 🛡️ Robust Error Handling: Automatic retries with exponential backoff
git clone https://github.com/sariola/notion2prompt.git
cd notion2prompt
cargo build --release- Go to https://www.notion.so/my-integrations
- Click "New integration" → Name it → Copy the token
- Open your Notion page → Click "Share" → Invite your integration
export NOTION_API_KEY="secret_your_api_key_here"
./target/release/notion2prompt https://notion.so/your-page --clipboardThe fastest way to install — pre-compiled wheels for Linux, macOS, and Windows:
# Install as a tool (adds `notion2prompt` to your PATH)
uv tool install notion2prompt
# Or run directly without installing
uvx notion2prompt https://notion.so/your-pagepip install notion2promptimport asyncio
import notion2prompt
# One-shot: fetch and render
prompt = asyncio.run(notion2prompt.fetch_and_render(
"https://www.notion.so/your-page-id",
api_key="secret_...",
))
print(prompt)
# Two-stage: fetch first, render later with different templates
content = asyncio.run(notion2prompt.fetch_content("your-page-id"))
prompt = notion2prompt.render_content(content, template="claude-xml")# Install directly from the repository
cargo install --git https://github.com/sariola/notion2prompt.gitRequires Rust (latest stable) and Cargo:
git clone https://github.com/sariola/notion2prompt.git
cd notion2prompt
cargo build --release
# Binary at target/release/notion2prompt- Go to https://www.notion.so/my-integrations
- Click "New integration"
- Give it a name and select the workspace
- Copy the "Internal Integration Token" (starts with
secret_)
- Open the Notion page you want to convert
- Click "Share" in the top right
- Invite your integration by name
- The integration needs read access
export NOTION_API_KEY="secret_your_api_key_here"# Using a Notion URL
notion2prompt https://www.notion.so/Your-Page-Title-abc123
# Using just the page ID
notion2prompt abc123def456ghi789
# With options
notion2prompt abc123 --output my-prompt.md --verbosenotion2prompt [OPTIONS] <NOTION_INPUT>
Arguments:
<NOTION_INPUT> Notion page/database ID or URL
Options:
-o, --output <FILE> Output file path
-t, --template <NAME> Template name [default: claude-xml]
-b, --clipboard Copy output to clipboard
-p, --pipe Output to stdout for piping
-d, --depth <N> Max recursion depth [default: 5]
-l, --limit <N> Max items to fetch [default: 1000]
-v, --verbose Enable verbose output
--content-dir <DIR> Content directory path
--instruction <TEXT> Additional instructions
--parse-child-pages Parse child pages recursively
--separate-child-page Keep child pages separate
--always-fetch-databases Always fetch database content
-h, --help Print help
-V, --version Print version
# Convert a page and save to file
notion2prompt https://notion.so/My-Page-123abc -o my-page.md
# Copy to clipboard with custom depth
notion2prompt 123abc --clipboard --depth 10
# Pipe to another command
notion2prompt 123abc --pipe | wc -l
# Use verbose mode for debugging
notion2prompt 123abc --verbose
# Parse child pages with custom template
notion2prompt 123abc --parse-child-pages --template defaultTemplates use Handlebars syntax and are stored in the templates/ directory. The default template is claude-xml.hbs.
claude-xml.hbs- Optimized for Claude AI with XML-style sectionsdefault.hbs- Simple markdown output
Available variables in your templates:
{{absolute_content_path}}- Full path to content{{source_tree}}- File tree representation
Note: Check template files for the complete list of available variables
notion2prompt uses a three-stage pipeline architecture optimized for performance and reliability:
Parallel content retrieval with intelligent work distribution:
- Work-stealing queues with priority scheduling
- Child database fetches get critical priority
- Atomic work tracking ensures completion
- Rate limiting and automatic retries
Pure data transformation without side effects:
- Visitor pattern for block processing
- Embedded database formatting
- Rich text and mention handling
- Property value formatting
Template rendering and output generation:
- Handlebars template rendering
- File writing, clipboard, or pipe output
- Path validation and sandboxing
Child databases (inline databases in Notion) are automatically detected and embedded within their parent pages, ensuring complete content capture. This critical feature uses priority scheduling to guarantee child content is fetched before worker threads terminate.
The parallel fetcher uses work-stealing to optimize CPU utilization. Multiple workers process API requests concurrently, automatically balancing load by stealing work from busy queues.
Every domain concept is wrapped in a newtype for compile-time validation:
let id = NotionId::parse("https://notion.so/page-123")?; // Validates format
let key = ApiKey::parse("secret_abc123")?; // Ensures proper prefixPerformance characteristics (typical results may vary based on network conditions and content complexity):
- Network throughput: Limited by Notion API rate limits
- CPU processing: Optimized for high-throughput data transformation
- Memory efficiency: String interning and object pooling reduce memory usage
- Concurrency: Work-stealing parallelism improves utilization over sequential processing
NOTION_API_KEY- Your Notion API key (required)RUST_LOG- Log level (debug, info, warn, error)
- Default recursion depth: 5 levels
- Default item limit: 1000 items
- Maximum safe recursion depth: 50 levels
# Debug build
cargo build
# Release build with optimizations
cargo build --release
# Run tests
cargo test
# Run with logging
RUST_LOG=debug cargo run -- <notion-id># Format code
cargo fmt
# Run linter
cargo clippy
# Type check
cargo check# Run all tests
cargo test
# Run specific test module
cargo test api::
# Run integration tests
cargo test --test integration
# Run with output
cargo test -- --nocapture-
"Invalid API key"
- Ensure your key starts with
secret_ - Verify the environment variable is set:
echo $NOTION_API_KEY
- Ensure your key starts with
-
"Object not found"
- Verify the page is shared with your integration
- Check the page/database ID is correct
- Ensure your integration has read access
-
"Rate limit exceeded"
- The tool automatically retries with exponential backoff
- Try reducing parallelism if issues persist
-
Missing child databases
- Ensure depth is greater than 0
- Use
--verboseto see fetch details - Check that child databases are properly linked in Notion
# Enable debug logging
RUST_LOG=debug notion2prompt <id> --verbose
# For development debugging, logs may be written to temporary files
# Check your system's temp directory if debug logging is enabledSee ARCHITECTURE.md for detailed architecture documentation.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
cargo test) - Format code (
cargo fmt) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
This project is built on the shoulders of excellent open-source libraries. Thanks to all their maintainers and contributors:
- tokio by the Tokio team — async runtime powering all concurrent operations
- reqwest by Sean McArthur (@seanmonstar) — ergonomic HTTP client
- hyper by Sean McArthur — the HTTP implementation beneath reqwest
- crossbeam by the Crossbeam team — work-stealing queues and concurrent primitives
- rayon by Niko Matsakis & Josh Stone — parallel iterators
- dashmap by Acrimon (@xacrimon) — concurrent hash map
- parking_lot by Amanieu d'Antras — fast mutex and RwLock
- im by Bodil Stokke (@bodil) — immutable data structures
- serde by David Tolnay (@dtolnay) & Erick Tryzelaar — serialization framework
- serde_json by David Tolnay — JSON support
- handlebars-rust by Ning Sun (@sunng87) — template engine
- clap by the clap contributors — command-line argument parsing
- thiserror by David Tolnay — derive macro for error types
- anyhow by David Tolnay — flexible error handling
- notion-client by jhamill34 — Notion API client for Rust
- PyO3 by the PyO3 team — Rust ↔ Python FFI bindings
- maturin by the PyO3 team — build system for Rust Python extensions
- pyo3-async-runtimes — async/await bridge between Rust and Python
- chrono by the Chronotope team — date and time handling
- regex by Andrew Gallant (@BurntSushi) — regular expressions
- indexmap by the indexmap contributors — insertion-ordered map
- lru by Jerome Froelich — LRU cache for API response caching
- arboard by 1Password — cross-platform clipboard access
- uuid by the uuid contributors — UUID generation and parsing
- url by the Servo project — URL parsing and validation