███╗ ██╗ ██████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██████╗██╗ ██╗ ████╗ ██║██╔═══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝██║ ██║ ██╔██╗ ██║██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║ ██║ ██║ ██║ ██║╚██╗██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██║ ██║ ██║ ██║ ╚████║╚██████╔╝ ██║ ██║╚██████╔╝██║ ╚████║ ╚██████╗███████╗██║ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝
IMPORTANT NOTICE:
This is an independent, unofficial command-line tool for working with Notion's API. This project is not affiliated with, endorsed by, or sponsored by Notion Labs, Inc. "Notion" is a registered trademark of Notion Labs, Inc.
Notion CLI for AI Agents & Automation -- a single Go binary, no runtime dependencies.
A powerful command-line interface for the Notion API, optimized for AI coding assistants and automation scripts. Built in Go with Cobra, distributed as a single ~8MB binary.
Key Features:
- Single binary: ~8MB, zero runtime dependencies, instant startup
- OAuth login:
notion-cli auth login-- authenticate in your browser, no token management - AI-first design: JSON envelope output, structured errors, exit codes
- Non-interactive: Perfect for scripts and automation
- Flexible output: JSON, CSV, table, or raw API responses
- Reliable: Automatic retry with exponential backoff for 408/429/5xx
- Intelligent caching: In-memory TTL cache with per-resource-type TTLs
- Schema discovery: AI-friendly database schema extraction
- Workspace caching: Fast database lookups without API calls
- Smart ID resolution: Automatic database_id / data_source_id conversion
- Cross-platform: macOS (arm64, amd64), Linux (amd64, arm64), Windows (amd64)
- Near-zero supply chain risk: 2 Go dependencies (cobra, pflag) vs 573 npm packages in v5.x
Complete rewrite from TypeScript/oclif to Go/Cobra.
All 26 commands have been ported with identical syntax -- existing scripts work unchanged. The JSON envelope format ({success, data, metadata}) and environment variables (NOTION_TOKEN) are the same.
Why Go?
- Single binary distribution (~8MB) instead of 573 npm dependencies
- Instant startup with no Node.js runtime overhead
- Cross-compilation: one build produces 5 platform binaries
- Near-zero supply chain risk: 2 Go dependencies vs hundreds of npm packages
v6.1.0: OAuth Authentication -- notion-cli auth login opens your browser, you authorize, done. No more copying tokens manually.
Technical details:
- 36 Go source files, ~9,800 lines of code
- 196 tests across 9 test suites, all passing
- ~8MB binary (stripped, darwin/arm64)
Deferred to Phase 2: Disk cache, request deduplication, circuit breaker, simple properties (-S flag), recursive page retrieval, markdown output from page content, interactive init wizard, update notifications.
See CHANGELOG.md for full details.
Option 1: npm (recommended for most users)
npm install -g @coastal-programs/notion-cliThis installs a thin npm wrapper that downloads the correct platform-specific binary automatically. Requires Node.js >= 18 at install time only -- the binary itself has no Node.js dependency.
Option 2: Go install (from source)
go install github.com/Coastal-Programs/notion-cli/cmd/notion-cli@latestRequires Go 1.21+.
Option 3: Build from source
git clone https://github.com/Coastal-Programs/notion-cli.git
cd notion-cli
make build
# Binary is at build/notion-cliOption A: OAuth login (recommended)
# Authenticate via your browser -- no token needed
notion-cli auth login
# Verify connectivity
notion-cli whoamiOption B: Manual token (CI/automation)
# Set your Notion API token
export NOTION_TOKEN="secret_your_token_here"
# Or save it to the config file
notion-cli config set-token <YOUR_TOKEN>Get a manual token: https://developers.notion.com/docs/create-a-notion-integration
# Sync your workspace for local database lookups
notion-cli sync# List your databases
notion-cli list --output json
# Discover database schema
notion-cli db schema <DATABASE_ID> --output json
# Query a database
notion-cli db query <DATABASE_ID> --output json
# Create a page
notion-cli page create --database-id <DATABASE_ID> \
--properties '{"Name": {"title": [{"text": {"content": "My Task"}}]}}'
# Search the workspace
notion-cli search "project" --output jsonAll commands support --output json for machine-readable responses.
# Log in via OAuth (opens browser)
notion-cli auth login
# Check current auth status
notion-cli auth status
# Log out (clear stored OAuth tokens)
notion-cli auth logout# Test connectivity and show bot info
notion-cli whoami
# Health check and diagnostics
notion-cli doctor
# Configure token manually (for CI/scripts)
notion-cli config set-token <TOKEN>
# Get config value
notion-cli config get <KEY>
# Show config file path
notion-cli config path
# View cache statistics
notion-cli cache info# Retrieve database metadata
notion-cli db retrieve <DATABASE_ID>
# Query database with filters
notion-cli db query <DATABASE_ID> --output json
notion-cli db query <DATABASE_ID> --filter '{"property": "Status", "select": {"equals": "Done"}}'
# Create new database
notion-cli db create \
--parent-page <PAGE_ID> \
--title "My Database" \
--properties '{"Name": {"type": "title"}}'
# Update database
notion-cli db update <DATABASE_ID> --title "New Title"
# Extract schema (AI-friendly)
notion-cli db schema <DATABASE_ID> --output json# Create page in database
notion-cli page create \
--database-id <DATABASE_ID> \
--properties '{"Name": {"title": [{"text": {"content": "Task"}}]}}'
# Retrieve page
notion-cli page retrieve <PAGE_ID> --output json
# Update page properties
notion-cli page update <PAGE_ID> \
--properties '{"Status": {"select": {"name": "Done"}}}'
# Get page property item
notion-cli page property-item <PAGE_ID> --property-id <PROPERTY_ID># Retrieve block
notion-cli block retrieve <BLOCK_ID>
# Get block children
notion-cli block children <BLOCK_ID> --output json
# Append children to block
notion-cli block append <BLOCK_ID> \
--children '[{"object": "block", "type": "paragraph", "paragraph": {"rich_text": [{"text": {"content": "Hello"}}]}}]'
# Update block
notion-cli block update <BLOCK_ID> --content "Updated text"
# Delete block
notion-cli block delete <BLOCK_ID># List all users
notion-cli user list --output json
# Retrieve user
notion-cli user retrieve <USER_ID>
# Get bot user info
notion-cli user bot# Search workspace
notion-cli search "project" --output json
# Search with filter
notion-cli search "docs" --filter page# Sync workspace (cache all accessible databases)
notion-cli sync
# List cached databases
notion-cli list --output json
# Batch retrieve multiple objects
notion-cli batch retrieve --ids <ID1>,<ID2>,<ID3>Use --filter with JSON matching the Notion API filter format:
# Filter by select property
notion-cli db query <ID> \
--filter '{"property": "Status", "select": {"equals": "Done"}}' \
--output json
# Compound AND filter
notion-cli db query <ID> \
--filter '{"and": [{"property": "Status", "select": {"equals": "Done"}}, {"property": "Priority", "number": {"greater_than": 5}}]}' \
--output json
# OR filter
notion-cli db query <ID> \
--filter '{"or": [{"property": "Tags", "multi_select": {"contains": "urgent"}}, {"property": "Tags", "multi_select": {"contains": "bug"}}]}' \
--output json
# Date filter
notion-cli db query <ID> \
--filter '{"property": "Due Date", "date": {"on_or_before": "2026-12-31"}}' \
--output jsonAll commands support multiple output formats via the --output flag:
# JSON (structured envelope format)
notion-cli db query <ID> --output json
# Table (default, human-readable)
notion-cli db query <ID> --output table
# CSV
notion-cli db query <ID> --output csv
# Raw API response (no envelope wrapping)
notion-cli db query <ID> --rawAll JSON output uses a consistent envelope:
{
"success": true,
"data": { ... },
"metadata": {
"object": "database",
"request_id": "abc-123"
}
}Error responses follow the same structure:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Database not found",
"suggestion": "Verify the database ID and check that your integration has access."
}
}0-- Success1-- Notion API error2-- CLI error (invalid flags, missing arguments, etc.)
Every command supports --output json for structured, parseable output:
# Get structured data
notion-cli db query <ID> --output json | jq '.data.results[].properties'
# Error responses are also structured JSON
notion-cli db retrieve invalid-id --output jsonExtract complete database schemas in AI-friendly formats:
# Get full schema
notion-cli db schema <DATABASE_ID> --output json
# Example output:
# {
# "success": true,
# "data": {
# "database_id": "...",
# "title": "Tasks",
# "properties": {
# "Name": { "type": "title" },
# "Status": {
# "type": "select",
# "options": ["Not Started", "In Progress", "Done"]
# }
# }
# }
# }No need to worry about database_id vs data_source_id confusion. The CLI automatically detects and converts between them:
# Both work -- use whichever ID you have
notion-cli db retrieve 1fb79d4c71bb8032b722c82305b63a00 # database_id
notion-cli db retrieve 2gc80e5d82cc9043c833d93416c74b11 # data_source_idCache your entire workspace locally for instant database lookups:
# One-time sync
notion-cli sync
# Now use database names instead of IDs
notion-cli db query "Tasks Database" --output json
# Browse all cached databases
notion-cli list --output jsonAI agents can check data freshness before operations:
notion-cli cache info --output jsonCache TTLs by resource type:
- Databases: 10 minutes
- Pages: 1 minute
- Users: 1 hour
- Blocks: 30 seconds
The CLI supports three authentication methods, checked in this order:
| Priority | Method | Use case |
|---|---|---|
| 1 | NOTION_TOKEN env var |
CI/CD, scripts, automation |
| 2 | OAuth token (from auth login) |
Interactive / daily use |
| 3 | Manual token (from config set-token) |
Fallback |
# Recommended: OAuth login
notion-cli auth login
# For CI/automation: environment variable
export NOTION_TOKEN=secret_your_token_here
# Check which method is active
notion-cli auth statusThe CLI reads configuration from ~/.config/notion-cli/config.json. You can manage it with:
# Set a value
notion-cli config set-token <TOKEN>
# Get a value
notion-cli config get <KEY>
# Show config file path
notion-cli config path#!/bin/bash
# Create a task and mark it complete
TASK_ID=$(notion-cli page create \
--database-id "$TASKS_DB_ID" \
--properties '{
"Name": {"title": [{"text": {"content": "Review PR"}}]},
"Status": {"select": {"name": "In Progress"}}
}' \
--output json | jq -r '.data.id')
echo "Created task: $TASK_ID"
# Do work...
# Mark complete
notion-cli page update "$TASK_ID" \
--properties '{"Status": {"select": {"name": "Done"}}}' \
--output json#!/bin/bash
# Export schema from one database, create another
notion-cli db schema "$SOURCE_DB" --output json > schema.json
notion-cli db create \
--parent-page "$TARGET_PAGE" \
--title "Migrated Database" \
--properties "$(jq '.data.properties' schema.json)" \
--output json#!/bin/bash
# Sync workspace and generate report
notion-cli sync
notion-cli list --output json > databases.json
echo "# Database Report - $(date)" > report.md
jq -r '.data[] | "- **\(.title)** (\(.id))"' databases.json >> report.mdThe CLI auto-resolves database_id vs data_source_id. If it still fails, verify your integration has access to the database in Notion's integration settings.
The CLI handles this automatically with exponential backoff and jitter. Retry behavior covers HTTP 408, 429, and 5xx responses.
# Check auth status
notion-cli auth status
# Re-authenticate via OAuth
notion-cli auth login
# Or verify your token env var is set
echo $NOTION_TOKEN
# Test connectivity
notion-cli whoami
# Ensure integration has access to the pages/databases you need:
# https://www.notion.so/my-integrations- Use filters to reduce data:
--filter '{"property": "Status", "select": {"equals": "Active"}}' - Sync your workspace first:
notion-cli sync - Use
--output jsonfor faster parsing than table output
notion-cli is built in Go with a focus on simplicity, reliability, and minimal dependencies.
- CLI framework: Cobra for command parsing and flag handling
- HTTP client: Raw
net/httpwith gzip support -- no Notion SDK dependency - Caching: In-memory TTL cache with per-resource-type expiration
- Retry: Exponential backoff with jitter for transient failures (408/429/5xx)
- Errors: 40+ structured error codes with human-readable suggestions
- Output: JSON envelope, ASCII table, CSV via
pkg/output.Printer - Config: Environment variables + JSON config file (
~/.config/notion-cli/config.json) - Distribution: npm wrapper with platform-specific binary packages (esbuild-style pattern)
| Dependency | Purpose |
|---|---|
github.com/spf13/cobra |
CLI framework |
github.com/spf13/pflag |
Flag parsing (indirect, via cobra) |
| Go standard library | Everything else |
- Go 1.21+ (the go.mod specifies 1.25, but the project targets 1.21+ compatibility)
- Git
- Make
- (Optional) golangci-lint for extended linting
git clone https://github.com/Coastal-Programs/notion-cli.git
cd notion-cli
make build# Build the binary to build/notion-cli
make build
# Run tests
make test
# Lint (go vet + golangci-lint if installed)
make lint
# Format code
make fmt
# Tidy module dependencies
make tidy
# Install to $GOPATH/bin
make install
# Cross-compile for all platforms
make release
# Clean build artifacts
make cleannotion-cli/
├── cmd/notion-cli/
│ └── main.go # Entry point
├── internal/
│ ├── cli/
│ │ ├── root.go # Cobra root command + global flags
│ │ └── commands/
│ │ ├── db.go # db query, retrieve, create, update, schema
│ │ ├── page.go # page create, retrieve, update, property_item
│ │ ├── block.go # block append, retrieve, delete, update, children
│ │ ├── user.go # user list, retrieve, bot
│ │ ├── search.go # search command
│ │ ├── sync.go # workspace sync
│ │ ├── list.go # list cached databases
│ │ ├── batch.go # batch retrieve
│ │ ├── whoami.go # connectivity check
│ │ ├── doctor.go # health checks
│ │ ├── auth.go # auth login, logout, status (OAuth)
│ │ ├── config.go # config get/set/path
│ │ └── cache_cmd.go # cache info/stats
│ ├── oauth/
│ │ └── oauth.go # OAuth flow (localhost server, token exchange)
│ ├── notion/
│ │ └── client.go # HTTP client, auth, request/response
│ ├── cache/
│ │ ├── cache.go # In-memory TTL cache
│ │ └── workspace.go # Workspace database cache
│ ├── retry/
│ │ └── retry.go # Exponential backoff with jitter
│ ├── errors/
│ │ └── errors.go # NotionCLIError with codes, suggestions
│ ├── config/
│ │ └── config.go # Config loading (env vars + JSON file)
│ └── resolver/
│ └── resolver.go # URL/ID/name resolution
├── pkg/
│ └── output/
│ ├── output.go # JSON/text/table/CSV formatting
│ ├── envelope.go # Envelope wrapper
│ └── table.go # Table formatter
├── go.mod
├── go.sum
├── Makefile
├── package.json # npm distribution wrapper
└── docs/ # Documentation
- All commands use Cobra; registered via
Register*Commands(root *cobra.Command) - Use
pkg/output.Printerfor all output -- neverfmt.Printlndirectly - Use
internal/errors.NotionCLIErrorfor errors -- never raw errors - Use envelope format for JSON output:
{success, data, metadata} - Use
internal/resolver.ExtractID()for all ID/URL inputs - Use
context.Contextfor all API calls
# Run all tests with verbose output
make test
# Run a specific test package
go test ./internal/cache/... -v
# Run a specific test
go test ./internal/cache/... -v -run TestCacheExpiry196 tests across 9 test suites.
See CONTRIBUTING.md for guidelines on:
- Code style and conventions
- Test requirements
- Pull request process
- Commit message format (conventional commits:
feat:,fix:,test:, etc.)
"Notion" is a registered trademark of Notion Labs, Inc. This project is an independent, unofficial tool and is not affiliated with, endorsed by, or sponsored by Notion Labs, Inc.
This project is licensed under the MIT License -- see the LICENSE file for details.
This project uses open-source dependencies. See NOTICE for complete third-party license information.
- Issues: Report bugs on GitHub Issues
- Discussions: Ask questions in GitHub Discussions
- Documentation: Full guides in the
/docsfolder
- Notion API: https://developers.notion.com
- Cobra: https://github.com/spf13/cobra
Built for AI agents, optimized for automation. A single Go binary -- no runtime dependencies.