A CLI is already a native agent affordance. exec("notion ...") with
JSON output is more direct than wrapping the same calls in MCP's
protocol layer, tool descriptors, and stdio lifecycle. Adding an MCP
surface would just expand maintenance load without giving agents
anything they can't already do.
Add this to Non-goals so the position is clear in the spec, not just
in a closed tracking issue.
12 KiB
Notion CLI — Design Document
Build something worthy of standing next to
gh.
Philosophy
- Commands map to concepts, not API endpoints. Users think in pages, databases, blocks — not HTTP verbs.
- Sensible defaults, full control. Simple operations should be one-liners. Complex operations should be possible.
- Two audiences: Humans (pretty output, interactive prompts) and Agents/Scripts (JSON output, piping, zero interactivity).
- Offline-first thinking. Cache what makes sense. Don't hit the API when you don't have to.
Non-goals
Things this project will not do, so the scope stays tight and the maintenance load stays sane:
- OAuth / public-integration login. Notion's
POST /v1/oauth/tokenonly supportsauthorization_codeandrefresh_tokengrant types — no PKCE, no device flow, no secretless public-client mode. Shipping OAuth in a publicly-distributed OSS binary would require either hardcodingclient_secret(defeats the purpose) or running a hosted token-exchange proxy (ongoing infra commitment). Internal integration tokens via--with-tokenorNOTION_TOKENcover the actual use cases. - Notion Workers / runtime deployment. Workers belong to the official
ntnCLI — they're tied to Notion-hosted infrastructure that only Notion can provision. We won't shadow that surface. - MCP server mode. A CLI is already a native agent affordance.
exec("notion db query <id> --format json")with structured output is more direct than wrapping the same calls in MCP's protocol layer, tool descriptors, and stdio lifecycle. We'd add surface area without giving agents anything they can't already do. Other projects own that flavor; we stay a CLI. - A Notion clone in the terminal. This is a CLI for the Notion API, not a TUI for browsing your workspace.
- Schema migrations / data-modeling DSL. Out of scope; use the Notion UI or a real migration tool.
Relationship to Notion's official ntn CLI
Notion launched its own CLI (ntn) in 2026, focused on Workers deployment with convenience commands for pages, files, and the raw API. The two tools coexist deliberately:
ntnowns first-party auth (OAuth + keychain), Workers deployment, and the official update channel.notion-cliowns broad resource CRUD (blocks, comments, users, search, full database CRUD), Windows support, fully open-source distribution, and an API surface tuned for scripts and AI agents.
When Notion ships features that obsolete a piece of notion-cli, we remove that piece rather than fight the duplicate.
Name
notion — short, obvious, no ambiguity. Installable as a single binary.
Authentication
notion auth login --with-token # Read an integration token from stdin
notion auth logout
notion auth status # Show current auth state
notion auth switch # Switch between multiple workspaces
Store tokens in OS keychain (like gh) with fallback to ~/.config/notion/credentials.json. Also support NOTION_TOKEN env var for CI/agent use.
See Non-goals for why OAuth login is not on the roadmap.
Command Structure
Core Resources
notion page # Work with pages
notion db # Work with databases
notion block # Work with content blocks
notion search # Search across workspace
notion user # User information
notion comment # Comments on pages/blocks
notion file # File uploads
notion page — Work with Pages
notion page view <page-id|url> # Display page content (rendered markdown)
notion page create <parent-id> --title "X" # Create page under parent
notion page edit <page-id> # Open in $EDITOR (markdown round-trip)
notion page delete <page-id> # Archive (soft delete)
notion page move <page-id> --to <parent> # Move page to new parent
notion page list <parent-id> # List child pages
notion page props <page-id> # Show page properties
notion page set <page-id> <prop>=<value> # Set a property value
notion page open <page-id|url> # Open in browser
Design notes:
viewrenders page blocks as readable markdown in terminaleditdownloads as markdown, opens editor, diffs, patches back- Accepts both UUID and full Notion URLs (auto-extract ID)
--format jsonon any command for machine-readable output
notion db — Work with Databases
notion db view <db-id|url> # Show database schema
notion db query <db-id> [--filter ...] [--sort ...] # Query rows
notion db create <parent-id> --title "X" # Create database
notion db update <db-id> # Update database schema
notion db add <db-id> <prop=value ...> # Add a row (page with properties)
notion db list # List accessible databases
notion db export <db-id> [--format csv|json|md] # Export database
notion db open <db-id|url> # Open in browser
Filter syntax (inspired by jq/gh):
notion db query <id> --filter 'Status=Done' --filter 'Priority=High'
notion db query <id> --filter 'Date>=2026-01-01' --sort 'Date:desc'
notion db query <id> --filter 'Tags~=backend' --limit 10
Operators: = != >= <= > < ~= (contains) !~= (not contains) ?= (is_empty) !?= (is_not_empty)
notion block — Work with Content Blocks
notion block list <parent-id> # List child blocks
notion block get <block-id> # Get a specific block
notion block append <parent-id> [--type paragraph] "text" # Add block
notion block delete <block-id> # Delete a block
notion block move <block-id> --after <id> # Reposition block
Block types for append: paragraph, heading1, heading2, heading3, todo, bullet, numbered, toggle, code, quote, divider, callout, image, bookmark
Pipe-friendly:
echo "Hello world" | notion block append <page-id>
cat notes.md | notion block append <page-id> --format markdown
notion search — Search
notion search "query" # Search pages and databases
notion search "query" --type page # Only pages
notion search "query" --type database # Only databases
notion search "query" --sort last_edited # Sort by edit time
notion comment — Comments
notion comment list <page-id> # List comments on page
notion comment add <page-id> "text" # Add comment
notion comment reply <comment-id> "text" # Reply to comment
notion user — Users
notion user me # Current bot user info
notion user list # List workspace users
notion user get <user-id> # Get user details
notion file — File Uploads
notion file upload <file-path> [--to <page-id>] # Upload file
notion file list # List uploads
notion api — Raw API Access (escape hatch)
notion api GET /v1/users/me # Raw API call
notion api POST /v1/search --body '{"query":"x"}'
Like gh api — for anything the CLI doesn't cover yet.
Global Flags
--format json|table|text|md # Output format (default: auto-detect tty)
--workspace <name> # Use specific workspace
--no-cache # Skip local cache
--debug # Show HTTP requests/responses
--quiet # Minimal output
--yes # Skip confirmations
Output Design
Human mode (tty detected):
$ notion page view abc123
📄 Project Roadmap
━━━━━━━━━━━━━━━━━
Last edited: 2 hours ago by @alice
## Phase 1
- [x] Design review
- [ ] Implementation
- [ ] Testing
Agent/Script mode (--format json or piped):
{
"id": "abc123",
"title": "Project Roadmap",
"last_edited": "2026-02-18T12:00:00Z",
"blocks": [...]
}
MVP Scope (v0.1.0)
Must have:
notion auth login --with-token+NOTION_TOKENenv varnotion searchnotion page view+notion page listnotion page create(simple: title + optional text body)notion db query(with basic filter/sort)notion db listnotion block list+notion block appendnotion api(raw escape hatch)- JSON + table + text output formats
- URL-to-ID auto-parsing
Post-MVP:
notion page edit(markdown round-trip)notion db add(property-typed row creation)notion db exportnotion commentcommandsnotion file uploadnotion page move- OS keychain auth storage
- Shell completions
- Local caching
Notion API → CLI Mapping
| API Endpoint | CLI Command |
|---|---|
| POST /v1/search | notion search |
| GET /v1/pages/{id} | notion page view <id> |
| POST /v1/pages | notion page create |
| PATCH /v1/pages/{id} | notion page set |
| POST /v1/pages/{id}/move | notion page move |
| GET /v1/pages/{id}/properties/{pid} | notion page props |
| GET /v1/databases/{id} | notion db view |
| POST /v1/databases | notion db create |
| PATCH /v1/databases/{id} | notion db update |
| POST /v1/data_sources/{id}/query | notion db query |
| GET /v1/blocks/{id} | notion block get |
| GET /v1/blocks/{id}/children | notion block list |
| PATCH /v1/blocks/{id}/children | notion block append |
| PATCH /v1/blocks/{id} | notion block update |
| DELETE /v1/blocks/{id} | notion block delete |
| GET /v1/comments | notion comment list |
| POST /v1/comments | notion comment add |
| GET /v1/users | notion user list |
| GET /v1/users/me | notion user me |
| GET /v1/users/{id} | notion user get |
| POST /v1/file_uploads | notion file upload |
| * | notion api <method> <path> |
Project Structure (Go)
notion-cli/
├── cmd/
│ ├── root.go # Root command, global flags
│ ├── auth.go # auth subcommands
│ ├── page.go # page subcommands
│ ├── db.go # db subcommands
│ ├── block.go # block subcommands
│ ├── search.go # search command
│ ├── comment.go # comment subcommands
│ ├── user.go # user subcommands
│ ├── file.go # file subcommands
│ └── api.go # raw api command
├── internal/
│ ├── client/ # Notion API client
│ │ ├── client.go # HTTP client, auth, retry
│ │ ├── pages.go # Page operations
│ │ ├── databases.go # Database operations
│ │ ├── blocks.go # Block operations
│ │ ├── search.go # Search
│ │ ├── users.go # User operations
│ │ └── comments.go # Comment operations
│ ├── render/ # Output formatting
│ │ ├── json.go # JSON output
│ │ ├── table.go # Table output
│ │ ├── markdown.go # Markdown rendering
│ │ └── text.go # Plain text
│ ├── config/ # Config & auth storage
│ │ └── config.go
│ └── util/
│ ├── url.go # URL/ID parsing
│ └── pagination.go # Auto-pagination helper
├── main.go
├── go.mod
├── go.sum
├── Makefile
├── README.md
└── LICENSE
Tech Choices
- CLI framework: cobra — same as
gh, industry standard - HTTP client:
net/http+ thin wrapper (no need for heavy SDK) - JSON:
encoding/jsonstandard lib - Table output: tablewriter or custom
- Color/styling: lipgloss or color
- Config: XDG-compliant,
~/.config/notion-cli/ - Build: goreleaser for cross-platform binaries + homebrew tap
Quality Bar
This must feel like a first-party tool. That means:
- Every error message is helpful (not just "request failed")
- Tab completion works
- Help text is clear and has examples
- Performance feels instant (<200ms for cached, <1s for API)
- Works in CI/CD pipelines without interactivity
- Works as an agent tool without human input