Build something worthy of standing next to
gh.
- 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.
notion — short, obvious, no ambiguity. Installable as a single binary.
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.
On OAuth / public integrations: not planned. 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). Neither is justified by current use cases, which are well served by private integration tokens via --with-token or NOTION_TOKEN. Will revisit if Notion adds CLI-friendly auth primitives.
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 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 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 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 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 me # Current bot user info
notion user list # List workspace users
notion user get <user-id> # Get user details
notion file upload <file-path> [--to <page-id>] # Upload file
notion file list # List uploads
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.
--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
$ notion page view abc123
📄 Project Roadmap
━━━━━━━━━━━━━━━━━
Last edited: 2 hours ago by @alice
## Phase 1
- [x] Design review
- [ ] Implementation
- [ ] Testing
{
"id": "abc123",
"title": "Project Roadmap",
"last_edited": "2026-02-18T12:00:00Z",
"blocks": [...]
}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
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
| 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> |
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
- 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
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