Feishu Doc Sync
飞书文档增量同步与表格操作指南。覆盖:表格更新策略(delete+insert)、header_row 设置、insert action、列宽算法、三阶段 diff 同步。当需要同步本地文档
Description
name: feishu-doc-sync description: | 飞书文档增量同步与表格操作指南。覆盖:表格更新策略(delete+insert)、header_row 设置、insert action、列宽算法、三阶段 diff 同步。当需要同步本地文档到飞书、操作飞书表格、或做增量编辑时激活。
Feishu Document Tool
Single tool feishu_doc with action parameter for all document operations, including table creation for Docx.
Token Extraction
From URL https://xxx.feishu.cn/docx/ABC123def → doc_token = ABC123def
Actions
Read Document
{ "action": "read", "doc_token": "ABC123def" }
Returns: title, plain text content, block statistics. Check hint field — if present, structured content (tables, images) exists that requires list_blocks.
Write Document (Replace All)
{ "action": "write", "doc_token": "ABC123def", "content": "# Title\n\nMarkdown content..." }
Replaces entire document with markdown content. Supports: headings, lists, code blocks, quotes, links, images ( auto-uploaded), bold/italic/strikethrough, and Markdown tables.
Append Content
{ "action": "append", "doc_token": "ABC123def", "content": "Additional content" }
Appends markdown to end of document. Supports the same content types as write, including Markdown tables.
Insert Content (Positioned)
{
"action": "insert",
"doc_token": "ABC123def",
"after_block_id": "doxcnXXX",
"content": "Markdown content including tables..."
}
Inserts content after a specific block. Uses Descendant API internally, supports all block types including tables.
Key usage: This is the primary way to insert content at a precise position. Use list_blocks first to find the after_block_id.
Create Document
{ "action": "create", "title": "New Document", "owner_open_id": "ou_xxx" }
With folder:
{
"action": "create",
"title": "New Document",
"folder_token": "fldcnXXX",
"owner_open_id": "ou_xxx"
}
Important: Always pass owner_open_id with the requesting user's open_id (from inbound metadata sender_id) so the user automatically gets full_access permission on the created document. Without this, only the bot app has access.
List Blocks
{ "action": "list_blocks", "doc_token": "ABC123def" }
Returns full block data including tables, images. Use this to read structured content and find block IDs for positioning.
Get Single Block
{ "action": "get_block", "doc_token": "ABC123def", "block_id": "doxcnXXX" }
Update Block Text
{
"action": "update_block",
"doc_token": "ABC123def",
"block_id": "doxcnXXX",
"content": "New text with **bold** and `code`"
}
Supports Markdown inline styles: **bold**, `code`, [link](url), ~~strike~~.
Supported block types: Text, Heading, Bullet, Ordered, Code, Quote, Todo.
⚠️ NOT for tables: Using update_block on a table cell replaces content as plain text, destroying all inline formatting (inline_code, bold, etc.).
Delete Block
{ "action": "delete_block", "doc_token": "ABC123def", "block_id": "doxcnXXX" }
Create Table
{
"action": "create_table",
"doc_token": "ABC123def",
"row_size": 2,
"column_size": 2,
"column_width": [200, 200]
}
⚠️ Positioning unreliable: parent_block_id and index parameters do not work correctly for tables — tables always end up at the document end. Use insert action with after_block_id instead for positioned tables.
Write Table Cells
{
"action": "write_table_cells",
"doc_token": "ABC123def",
"table_block_id": "doxcnTABLE",
"values": [
["A1", "B1"],
["A2", "B2"]
]
}
Writes plain text into table cells. Supports Markdown inline styles in cell values.
Note: This clears and rebuilds cell children. Suitable for initial table filling or full-cell replacement, but not for partial cell edits.
Create Table With Values (One-step)
{
"action": "create_table_with_values",
"doc_token": "ABC123def",
"row_size": 2,
"column_size": 2,
"column_width": [200, 200],
"values": [["A1", "B1"], ["A2", "B2"]]
}
⚠️ Same positioning caveat as create_table — use insert action for positioned tables.
Table Row/Column Operations
{ "action": "insert_table_row", "doc_token": "...", "table_block_id": "...", "row_index": -1 }
{ "action": "insert_table_column", "doc_token": "...", "table_block_id": "...", "column_index": -1 }
{ "action": "delete_table_rows", "doc_token": "...", "block_id": "...", "row_start": 1, "row_count": 1 }
{ "action": "delete_table_columns", "doc_token": "...", "block_id": "...", "column_start": 0, "column_count": 1 }
{ "action": "merge_table_cells", "doc_token": "...", "table_block_id": "...", "row_start": 0, "row_end": 2, "column_start": 0, "column_end": 2 }
Note for delete_table_rows: Uses block_id (not table_block_id), row_start (not row_index).
Upload Image
{ "action": "upload_image", "doc_token": "ABC123def", "url": "https://example.com/image.png" }
Or local path with position control:
{
"action": "upload_image",
"doc_token": "ABC123def",
"file_path": "/tmp/image.png",
"parent_block_id": "doxcnParent",
"index": 5
}
Or base64 data URI:
{
"action": "upload_image",
"doc_token": "ABC123def",
"image": "data:image/png;base64,iVBOR..."
}
Note: Image display size is determined by the uploaded image's pixel dimensions. For small images (e.g. 480x270), scale to 800px+ width before uploading.
Upload File Attachment
{ "action": "upload_file", "doc_token": "ABC123def", "url": "https://example.com/report.pdf" }
Or local path:
{ "action": "upload_file", "doc_token": "ABC123def", "file_path": "/tmp/report.pdf", "filename": "Q1-report.pdf" }
Color Text
{
"action": "color_text",
"doc_token": "ABC123def",
"block_id": "doxcnXXX",
"content": "colored text",
"color": "red"
}
Reading Workflow
- Start with
action: "read"— get plain text + statistics - Check
block_typesin response for Table, Image, Code, etc. - If structured content exists, use
action: "list_blocks"for full data
Incremental Sync Best Practices
When to Use Incremental vs Full Sync
- < 5 blocks changed → incremental (delete + insert individual blocks)
- > 80% blocks changed → full sync (
writeaction to replace all) - Single table changed → delete old table + insert new table
Table Update Strategy: Delete + Insert
Tables should NEVER be edited in-place. Always:
1. list_blocks → find the block before the table (anchor_block_id)
2. delete_block(block_id = old_table_id)
3. insert(after_block_id = anchor_block_id, content = "| new | table |...")
Why not edit in-place?
update_blockon table cell → strips inline formatting (inline_code, bold, links all become plain text)write_table_cells→ clears and rebuilds cell children, similar formatting losscreate_table/create_table_with_values→ positioning unreliable
Multi-Block Batch Replacement
When changes span multiple consecutive blocks:
- Find anchor (last unchanged block before the changed region)
- Delete changed blocks in reverse order (to avoid index drift)
- Single
insertcall with all new content (supports mixed block types)
Diff Strategy (from feishu-doc-sync)
- Block signature matching — (type, content) hash for alignment, style hash for change detection
- LCS alignment of local ↔ remote blocks
- Three-phase execution: UPDATE (text blocks) → DELETE (reverse order) → INSERT (forward order)
- Tables: if structure is identical, cell-level diff is possible; if structure differs, full table replacement
- When change rate > 80%, fall back to full sync
Table Header Row (Dark Background)
The header_row: true property gives tables a dark header background. This is a common requirement.
⚠️ Critical: header_row can ONLY be set at creation time
The PATCH API (update_table_property) does NOT work for header_row — it returns code: 0 (success) but silently fails to apply the property. This applies to both single-block PATCH and batch_update.
How to Create Tables with Header Row
Since insert action (Descendant API) and create_table action do not support header_row, you must use the raw Feishu API:
# Step 1: Get tenant_access_token
TOKEN=$(curl -s https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \
-H "Content-Type: application/json" \
-d '{"app_id":"'$APP_ID'","app_secret":"'$APP_SECRET'"}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['tenant_access_token'])")
# Step 2: Create empty table with header_row via document_block_children.create
curl -s -X POST \
"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children?document_revision_id=-1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"children": [{
"block_type": 31,
"table": {
"property": {
"row_size": 3,
"column_size": 2,
"header_row": true,
"column_width": [300, 400]
}
}
}],
"index": 5
}'
# Step 3: Fill cells via feishu_doc write_table_cells
# (use the new table's block_id from step 2 response)
Credentials: Read from /root/.openclaw/openclaw.json → channels.feishu.appId / channels.feishu.appSecret.
Workflow for Positioned Table with Header Row
list_blocks→ find the anchor block and target index- (If replacing)
delete_block→ remove old table - Raw API
document_block_children.create→ create empty table withheader_row: trueat correct index feishu_doc write_table_cells→ fill content- Verify:
get_block→ checktable.property.header_row
Column Width Algorithm
Auto-calculated when using insert action (Descendant API):
- Total width: ~730px
- CJK characters count as 2x width
- Per-column: proportional distribution, clamped to 50–400px
- When using raw API, specify
column_widthmanually
Sync Prerequisites
- Always
git pullbefore reading local repo files — ensures latest content - Compare before writing — use
readorlist_blocksto check current state - Preserve formatting — inline_code, bold, links in table cells require careful handling
Configuration
channels:
feishu:
tools:
doc: true # default: true
Note: feishu_wiki depends on this tool — wiki page content is read/written via feishu_doc.
Permissions
Required: docx:document, docx:document:readonly, docx:document.block:convert, drive:drive
Reviews (0)
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!