solid-notion CLI Guide
INLINECODE0 is a CLI for reading, editing, and writing Notion pages as Markdown with local reversible changesets.
0. Installation
For normal usage (published package):
CODEBLOCK0
For local development from source:
CODEBLOCK1
1. Authentication Setup
Before using any command that talks to Notion, a token must be configured.
Getting a Notion Token
Create a Notion integration to get an API token:
- 1. Visit https://www.notion.so/profile/integrations/internal
- Click "New integration" and give it a name
- Copy the "Internal Integration Token"
- Share your pages with this integration (via page "Share" settings)
Check current auth status
CODEBLOCK2
Returns:
CODEBLOCK3
If token_present is false, run init first.
Save a token (agent-recommended method)
CODEBLOCK4
Returns on success:
CODEBLOCK5
Other token input methods (in precedence order):
| Method | Flag | Notes |
|---|
| Direct | INLINECODE3 | Visible in ps / shell history |
| Stdin |
--token-stdin |
Recommended for agents |
| JSON |
--input-json '{"token":"..."}' | Useful for structured protocols |
Additional flags:
| Flag | Effect |
|---|
| INLINECODE7 | Machine-readable JSON output only |
| INLINECODE8 |
Preview without writing |
|
--force | Overwrite existing token |
|
--profile <name> | Use a named profile (default:
"default") |
Remove a token
CODEBLOCK6
2. Command Reference
2.1 Browse
List locally pulled pages
CODEBLOCK7
Lists all pages that have been pulled to $SOLID_NOTION_HOME/. Does not call the Notion API.
Default output: tab-separated <pulled_at>\t<page_id>\t<title>, sorted newest first.
JSON output (--json): array of objects with page_id, title, pulled_at, path.
List all pages (remote)
CODEBLOCK8
Output: tab-separated lines of <last_edited>\t<page_id>\t<title>.
Search pages
CODEBLOCK9
Output: same tab-separated format as pages.
2.2 Read
Show a page (non-recursive, stdout)
CODEBLOCK10
INLINECODE21 can be a UUID or a page title. Default format: markdown.
Show a block (JSON only)
CODEBLOCK11
Pull a page to local files
CODEBLOCK12
Options:
| Flag | Default | Description |
|---|
| INLINECODE23 | INLINECODE24 | INLINECODE25 or INLINECODE26 |
| INLINECODE27 |
$SOLID_NOTION_HOME/<page_id> | Output directory |
|
--no-local-images | (images downloaded) | Skip downloading images |
|
--no-local-videos | (videos downloaded) | Skip downloading videos |
|
--no-recursive | (recursive) | Only first-level blocks |
Outputs the path of the written file.
If the page was already pulled locally, running pull page again fetches the latest content from Notion and overwrites local output files in that directory.
Pull a block to local file
CODEBLOCK13
Options: --outdir <dir>, INLINECODE34
2.3 Edit and Write
All edit/write/submit commands require a strict Notion page ID (UUID format like aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee). Page names are NOT accepted.
Apply a JSON patch
Pipe a JSON patch object to stdin:
CODEBLOCK14
Also accepts a markdown file path (notion-page-<uuid>.md) to resolve the page ID.
CRITICAL: Valid Patch Schema
The patch object must have exactly these keys: ops (array) and notes (string). No other keys allowed.
CODEBLOCK15
Three allowed operation types:
replace_block_text
Replaces rich_text content of a block. Supported block types:
INLINECODE40 , heading_1, heading_2, heading_3, bulleted_list_item, numbered_list_item, to_do, quote, INLINECODE48
CODEBLOCK16
append_blocks
Appends new blocks under a parent block.
- - Most block types need
type + INLINECODE51 - INLINECODE52 needs only INLINECODE53
- INLINECODE54 supports optional
language (defaults to plain text)
CODEBLOCK17
Allowed block types:
- -
paragraph, heading_1, heading_2, INLINECODE60 - INLINECODE61 ,
numbered_list_item, to_do, quote, INLINECODE65 - INLINECODE66 , INLINECODE67
set_props
Updates page properties (title, richtext, number, checkbox, select, multiselect, date).
CODEBLOCK18
Property value types:
| Type | Shape | Example |
|---|
| INLINECODE69 | INLINECODE70 | - |
| INLINECODE71 |
{ "type": "checkbox", "value": true } | - |
|
select |
{ "type": "select", "name": "Option" } | - |
|
multi_select |
{ "type": "multi_select", "names": ["A", "B"] } | - |
|
date |
{ "type": "date", "start": "2026-03-02", "end": null } | end optional |
|
title |
{ "type": "title", "md": "markdown" } | - |
|
rich_text |
{ "type": "rich_text", "md": "markdown" } | - |
Write workspace changes back to Notion
CODEBLOCK19
Reads the workspace files (page.md, .original.md), replaces the page content in Notion, creates a changeset, and cleans up the workspace.
Submit pending edits with rollback
CODEBLOCK20
CRITICAL: How submit works
INLINECODE85 consumes pending edit logs (created by previous edit commands) and applies them to Notion with transaction-like semantics:
- 1. Phase 0: Load — Reads all edit log files from
edit-logs/<page_id>/ that don't have .submitted markers - Phase 1: Prepare — Fetches before-snapshots (current block content, property values) so rollback is possible
- Phase 2: Apply — Applies ops sequentially to Notion
- Phase 3: Finalize — On success, saves commit to
versions/. On failure, rolls back already-applied ops using before-snapshots.
Exit statuses:
| INLINECODE90 | Meaning | INLINECODE91 |
|---|
| INLINECODE92 | All ops applied successfully | INLINECODE93 |
| INLINECODE94 |
No pending edits found |
false |
|
rolled_back | Apply failed but rollback succeeded |
false |
|
failed_needs_reconcile | Apply failed AND rollback partially failed |
false |
Submit result format:
CODEBLOCK21
Best practices:
- - Always run
submit after edit operations to publish changes - If submit returns
rolled_back, the page is back to original state — no manual cleanup needed - If submit returns
failed_needs_reconcile, manual intervention may be required - The
-m (message) flag is required
2.4 Create New Pages
CRITICAL: Use the new command to create Notion pages programmatically
The new command creates a new page under a parent (page or database) with metadata only (title, icon, cover, props) via stdin as JSON. After creation, the page is automatically pulled locally as markdown. To add content (blocks), use the existing edit + submit workflow.
Create a new page
CODEBLOCK22
Required flags:
- -
--parent <parent_id> — Parent page or database ID - INLINECODE110 — Commit message
Optional flags:
- -
--database — Parent is a database (creates as database entry) - INLINECODE112 — Output JSON only
- INLINECODE113 — Validate without creating
CRITICAL: Valid Payload Schema
The JSON payload via stdin must have exactly these keys:
CODEBLOCK23
Required fields:
- -
title (string, non-empty) - INLINECODE115 (string, can be empty)
Optional fields:
- -
icon — { "type": "emoji", "emoji": "🎉" } or INLINECODE118 - INLINECODE119 — INLINECODE120
- INLINECODE121 — Page properties (database entries), same types as
set_props plus url, email, INLINECODE125
No blocks field. Content is added via edit + submit after creation.
Property types for props (database entries):
All types from set_props plus:
- -
url: INLINECODE132 - INLINECODE133 : INLINECODE134
- INLINECODE135 : INLINECODE136
CRITICAL rules:
- - Payload must have exactly
title, notes, and optional fields — no extra keys - For database pages,
props must match the database schema - After creation, the page is auto-pulled locally as markdown
Transaction semantics:
INLINECODE140 follows the same 4-phase pipeline as submit:
- 1. Phase 0: Parse — Read and validate JSON from stdin
- Phase 1: Prepare — Verify parent exists, derive internal ops, create commit draft
- Phase 2: Apply — Create page on Notion (metadata only)
- Phase 3: Finalize — Persist version record, auto-pull page. On failure, rollback (archive page).
Rollback for new:
- - Archives the created page (soft-delete)
Success output (JSON mode):
CODEBLOCK24
Dry run output:
CODEBLOCK25
2.5 History and Restore
View history
CODEBLOCK26
Output: tab-separated <id>\t<created_at>\t<type>.
Types:
- -
changeset — Created by write or INLINECODE146 - INLINECODE147 — Created by
new command - INLINECODE149 — Created by
submit command
Restore to a previous changeset or version
CODEBLOCK27
Behavior depends on type:
- - Changeset (from
write/restore): Restores page to that changeset's state - Version (
submit or new): Restore to the target hash by undoing only the later submit versions, then writes a new changeset
Hash-only lookup is supported. If the hash exists in multiple pages, CLI asks you to disambiguate with page ID.
After restore-to-version, local version files after the target hash are deleted.
Outputs the new changeset ID created by restore.
3. Agent Workflows
Workflow: Read a page as Markdown
CODEBLOCK28
Then read the output file path printed to stdout.
Workflow: Full edit cycle (Markdown mode)
- 1. Pull the page to a workspace:
CODEBLOCK29
- 2. Edit the local
page.md file as needed.
- 3. Write changes back:
CODEBLOCK30
- 4. Submit with a message:
CODEBLOCK31
Workflow: Edit via JSON patch (Programmatic mode)
Use this workflow when making precise, targeted edits without pulling full markdown.
- 1. Get the target block IDs (you may need to pull JSON first):
CODEBLOCK32
- 2. Construct a valid patch with
ops array and notes:
CODEBLOCK33
- 3. Apply the patch via stdin:
CODEBLOCK34
- 4. Submit the pending edits:
CODEBLOCK35
CRITICAL rules for JSON patch editing:
- - The patch must have exactly
ops and notes — no extra keys - Each op must have
op, reason, and type-specific fields - INLINECODE163 in
replace_block_text must be a valid Notion block ID - INLINECODE165 in
set_props must be the target page UUID - INLINECODE167 creates new blocks — the
created_block_ids are recorded in edit logs for rollback
Workflow: Batch multiple edits before submit
You can run multiple edit commands before a single submit. All pending edits are aggregated:
CODEBLOCK36
Submit reads all edit logs from edit-logs/<page_id>/, marks them .submitted on success, and creates a single commit record.
Workflow: Restore a change
- 1. List changesets:
CODEBLOCK37
- 2. Restore to a specific hash:
solid-notion restore <page_id> <changeset_or_commit_id>
# or hash-only when unique:
solid-notion restore <changeset_or_commit_id>
4. Storage Concepts & Layout
All data is stored under $SOLID_NOTION_HOME (default: ~/.local/share/solid-notion-cli).
Storage Concepts Explained
Workspace files (<page_id>/) — Created by pull, used by write:
- -
page.md — The markdown you edit - INLINECODE179 — Snapshot before editing (for diff/comparison)
- INLINECODE180 — Page metadata (properties, etc.)
- Lifecycle: Created on
pull, deleted on successful INLINECODE182
Edit logs (edit-logs/<page_id>/) — Created by edit, consumed by submit:
- - JSONL files recording each patch operation with before/after snapshots
- Purpose: Enable rollback if submit fails
- Format: Each line is a JSON object with
op, block_id/page_id, reason, and before/after values - Lifecycle: Created on
edit, marked .submitted on successful INLINECODE192
Versions (<page_id>/versions/) — Created by submit:
- - Commit records with full operation history and snapshots
- Purpose: Immutable history of what was published to Notion
- Format: JSON with
commitId, status, ops, beforeSnapshots, INLINECODE199 - Lifecycle: Created on every
submit attempt (even failures)
Changesets (changesets/<page_id>/) — Created by write and restore:
- - Reversible markdown diffs for the "write -> restore" workflow
- Purpose: Track full page content changes for
history and INLINECODE205 - Format: Markdown files with YAML frontmatter containing before/after content
- Lifecycle: Created on
write, referenced by history and INLINECODE208
Directory Layout
CODEBLOCK39
Override the base directory with SOLID_NOTION_HOME environment variable.
5. Global Flags
| Flag | Effect |
|---|
| INLINECODE210 | Print debug logs to stderr |
| INLINECODE211 |
Print version |
6. Error Handling
init command error codes
| Exit code | Error | Meaning |
|---|
| 2 | INLINECODE212 | No token provided |
| 3 |
invalid_token | Token is empty or invalid |
| 4 |
token_already_exists | Token exists, use
--force |
| 5 |
write_failed | Could not write config file |
General errors
All other commands exit 1 on failure and print diagnostics to stderr including error name, message, metadata (status, code, errno, syscall, hostname), cause chain, and stack trace.
7. Anti-Patterns
Authentication
- - Do NOT use
--token <value> in automated flows. Use --token-stdin to avoid leaking tokens in process lists. - Do NOT skip
solid-notion auth status --json before running commands. If there is no token, all Notion API calls will fail.
Page IDs
- - Do NOT pass page names to
edit, write, submit, history, or restore. These commands require strict page UUIDs.
Output parsing
- - Do NOT forget
--json when you need to parse command output programmatically.
Submit command
- - Do NOT call
submit without -m. The message flag is required. - Do NOT assume
submit takes stdin — it reads from local edit-logs/, not from stdin. - Do NOT ignore
status: "rolled_back" — this means Notion writes failed but were undone. - Do NOT ignore
status: "failed_needs_reconcile" — this means both apply AND rollback failed.
Edit patch schema
- - Do NOT include extra keys in the patch object (only
ops and notes allowed). - Do NOT forget the
reason field in each op — it is required. - Do NOT use unsupported block types in
replace_block_text or append_blocks. - Do NOT use
set_props on properties with unsupported types (only: number, checkbox, select, multiselect, date, title, richtext). - Do NOT pass Notion block objects as
new_markdown — pass simple Markdown text (e.g., "## Heading" or "Paragraph with bold").
solid-notion CLI 指南
solid-notion 是一个命令行工具,用于以 Markdown 格式读取、编辑和写入 Notion 页面,并支持本地可逆变更集。
0. 安装
常规使用(已发布包):
bash
npm install -g solid-notion
solid-notion --version
本地源码开发:
bash
pnpm install
pnpm build
1. 认证设置
在使用任何与 Notion 通信的命令之前,必须先配置令牌。
获取 Notion 令牌
创建一个 Notion 集成以获取 API 令牌:
- 1. 访问 https://www.notion.so/profile/integrations/internal
- 点击新建集成并为其命名
- 复制内部集成令牌
- 通过页面共享设置,将你的页面与此集成共享
检查当前认证状态
bash
solid-notion auth status --json
返回:
json
{
ok: true,
profile: default,
config_path: ...,
token_present: true,
token_fingerprint: a1b2c3d4,
token_valid: null
}
如果 token_present 为 false,请先运行 init。
保存令牌(代理推荐方法)
bash
printf %s $NOTION_TOKEN | solid-notion init --token-stdin --json
成功时返回:
json
{
ok: true,
action: init,
profile: default,
config_path: ...,
token_saved: true,
overwritten: false,
ignored_inputs: [],
dry_run: false
}
其他令牌输入方法(按优先级排序):
| 方法 | 标志 | 说明 |
|---|
| 直接 | --token <value> | 在 ps / shell 历史中可见 |
| 标准输入 |
--token-stdin |
推荐用于代理 |
| JSON | --input-json {token:...} | 适用于结构化协议 |
附加标志:
| 标志 | 效果 |
|---|
| --json | 仅输出机器可读的 JSON |
| --dry-run |
预览而不写入 |
| --force | 覆盖现有令牌 |
| --profile
| 使用指定配置文件(默认:default) |
移除令牌
bash
solid-notion auth logout --json
2. 命令参考
2.1 浏览
列出本地拉取的页面
bash
solid-notion ls
solid-notion ls --json
列出已拉取到 $SOLIDNOTIONHOME/ 的所有页面。不调用 Notion API。
默认输出:制表符分隔的 at>\tid>\t,按最新排序。
JSON 输出(--json):包含 pageid、title、pulledat、path 的对象数组。
列出所有页面(远程)
bash
solid-notion pages
输出:制表符分隔的 edited>\tid>\t 行。
搜索页面
bash
solid-notion search
输出:与 pages 相同的制表符分隔格式。
2.2 读取
显示页面(非递归,标准输出)
bash
solid-notion show page idor_name> --format markdown
solid-notion show page idor_name> --format json
idor_name> 可以是 UUID 或页面标题。默认格式:markdown。
显示块(仅 JSON)
bash
solid-notion show block --format json
拉取页面到本地文件
bash
solid-notion pull page idor_name> --format markdown --outdir ./output
选项:
| 标志 | 默认值 | 描述 |
|---|
| --format <format> | json | json 或 markdown |
| --outdir <dir> |
$SOLIDNOTIONHOME/ | 输出目录 |
| --no-local-images | (下载图片) | 跳过下载图片 |
| --no-local-videos | (下载视频) | 跳过下载视频 |
| --no-recursive | (递归) | 仅第一级块 |
输出写入文件的路径。
如果页面已本地拉取,再次运行 pull page 将从 Notion 获取最新内容并覆盖该目录中的本地输出文件。
拉取块到本地文件
bash
solid-notion pull block --outdir ./output
选项:--outdir
、--no-recursive
2.3 编辑和写入
所有编辑/写入/提交命令都需要严格的 Notion 页面 ID(UUID 格式,如 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)。不接受页面名称。
应用 JSON 补丁
通过标准输入管道传输 JSON 补丁对象:
bash
echo {ops: [...]} | solid-notion edit
也接受 Markdown 文件路径(notion-page-.md)以解析页面 ID。
关键:有效的补丁模式
补丁对象必须恰好包含以下键:ops(数组)和 notes(字符串)。不允许其他键。
json
{
ops: [
{ op: replaceblocktext, blockid: ..., newmarkdown: ..., reason: ... },
{ op: appendblocks, parentblock_id: ..., blocks: [...], reason: ... },
{ op: setprops, pageid: ..., set: {...}, reason: ... }
],
notes: 可选说明
}
三种允许的操作类型:
replaceblocktext
替换块的富文本内容。支持的块类型:
paragraph、heading1、heading2、heading3、bulletedlistitem、numberedlistitem、todo、quote、callout
json
{
op: replaceblocktext,
block_id: block-uuid,
new_markdown: ## 新标题,
reason: 澄清了章节标题
}
append_blocks
在父块下追加新块。
- - 大多数块类型需要 type + richtextmd
- divider 只需要 type
- code 支持可选的 language(默认为 plain text)
json
{
op: append_blocks,
parentblockid: parent-uuid,
blocks: [
{ type: paragraph, richtextmd: 新段落内容 },
{ type: heading2, richtext_md: 新章节 }
],
reason: 添加了结论部分
}
允许的块类型:
- - paragraph、heading1、heading2、heading3
- bulletedlistitem、numberedlistitem、todo、quote、callout
- code、divider
set_props
更新页面属性(标题、富文本、数字、复选框、选择、多选、日期)。
json
{
op: set_props,
page_id: page-uuid,
set: {
状态: { type: select, name: 已完成 },
优先级: { type: number, value: 3 },
已完成: { type: checkbox, value: true },
标签: { type: multi_select, names: [紧急, 重要] },
截止日期: { type: date, start: 2026-03-02, end: null },
标题: { type: title, md: 新页面标题 },
备注: { type: rich_text, md: 一些备注 }
},
reason: 将状态更新为已完成
}
属性值类型:
| 类型 | 结构 | 示例 |
|---|
| number | { type: number, value: 42 } | - |
| checkbox |
{ type: checkbox, value: true } | - |
| select | { type: select, name: 选项 } | - |
| multi_select |