Files
Fourier 7d1b8b286f docs(design): explicitly rule out MCP server mode
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.
2026-05-16 21:34:42 +08:00

12 KiB

Notion CLI — Design Document

Build something worthy of standing next to gh.

Philosophy

  1. Commands map to concepts, not API endpoints. Users think in pages, databases, blocks — not HTTP verbs.
  2. Sensible defaults, full control. Simple operations should be one-liners. Complex operations should be possible.
  3. Two audiences: Humans (pretty output, interactive prompts) and Agents/Scripts (JSON output, piping, zero interactivity).
  4. 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/token only supports authorization_code and refresh_token grant types — no PKCE, no device flow, no secretless public-client mode. Shipping OAuth in a publicly-distributed OSS binary would require either hardcoding client_secret (defeats the purpose) or running a hosted token-exchange proxy (ongoing infra commitment). Internal integration tokens via --with-token or NOTION_TOKEN cover the actual use cases.
  • Notion Workers / runtime deployment. Workers belong to the official ntn CLI — 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:

  • ntn owns first-party auth (OAuth + keychain), Workers deployment, and the official update channel.
  • notion-cli owns 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:

  • view renders page blocks as readable markdown in terminal
  • edit downloads as markdown, opens editor, diffs, patches back
  • Accepts both UUID and full Notion URLs (auto-extract ID)
  • --format json on 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 "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:

  1. notion auth login --with-token + NOTION_TOKEN env var
  2. notion search
  3. notion page view + notion page list
  4. notion page create (simple: title + optional text body)
  5. notion db query (with basic filter/sort)
  6. notion db list
  7. notion block list + notion block append
  8. notion api (raw escape hatch)
  9. JSON + table + text output formats
  10. URL-to-ID auto-parsing

Post-MVP:

  • notion page edit (markdown round-trip)
  • notion db add (property-typed row creation)
  • notion db export
  • notion comment commands
  • notion file upload
  • notion 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/json standard 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