feishu-doc-write
/install feishu-doc-write
Feishu Document Writer
Reference spec for writing content to Feishu (Lark) cloud documents via the Docx API. Feishu docs use a Block tree model — raw Markdown is not accepted.
Document (block_type=1, Page)
+-- Heading1 Block (block_type=3)
+-- Text Block (block_type=2)
+-- Callout Block (block_type=19)
| +-- Text Block
| +-- Bullet Block
+-- Image Block (block_type=27)
+-- Divider Block (block_type=22)
Preferred Approach: Convert API
Feishu provides an official Markdown -> Blocks conversion endpoint:
POST /open-apis/docx/v1/documents/{document_id}/convert
{
"content": "# Title\
\
Body text\
\
- Item 1\
- Item 2\
\
> Quote",
"content_type": "markdown"
}
Pros: No manual Block JSON construction. Handles most standard Markdown. Limitation: Does not support Feishu-specific blocks (Callout, etc.) — use manual Block creation for those.
Block Type Reference
| block_type | Name | JSON Key | Notes |
|---|---|---|---|
| 1 | Page | page |
Document root |
| 2 | Text | text |
Paragraph |
| 3-11 | Heading1-9 | heading1-heading9 |
Headings |
| 12 | Bullet | bullet |
Unordered list (each item = separate block) |
| 13 | Ordered | ordered |
Ordered list |
| 14 | Code | code |
Code block (with style.language enum) |
| 15 | Quote | quote |
Blockquote |
| 17 | Todo | todo |
Checkbox item (with style.done) |
| 19 | Callout | callout |
Highlight box (Feishu-specific, container block) |
| 22 | Divider | divider |
Horizontal rule |
| 27 | Image | image |
Two-step: create placeholder, then upload |
| 31 | Table | table |
Table |
| 34 | QuoteContainer | quote_container |
Quote container |
Create Blocks API
POST /open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children?document_revision_id=-1
Headers:
Content-Type: application/json
Authorization: Bearer \x3Ctenant_access_token>
Body:
{
"children": [ ...Block array... ],
"index": 0
}
block_id: Parent block ID (usuallydocument_iditself for root)index: Insert position (0 = beginning, -1 or omit = end)
Block JSON Examples
Text
{
"block_type": 2,
"text": {
"elements": [{
"text_run": {
"content": "Paragraph text here",
"text_element_style": { "bold": false, "italic": false }
}
}]
}
}
Heading
{ "block_type": 3, "heading1": { "elements": [{ "text_run": { "content": "H1 Title" } }] } }
{ "block_type": 4, "heading2": { "elements": [{ "text_run": { "content": "H2 Title" } }] } }
Bullet / Ordered List
{ "block_type": 12, "bullet": { "elements": [{ "text_run": { "content": "List item" } }] } }
{ "block_type": 13, "ordered": { "elements": [{ "text_run": { "content": "Numbered item" } }] } }
Each list item is a separate Block.
Code Block
{
"block_type": 14,
"code": {
"elements": [{ "text_run": { "content": "console.log('hello');" } }],
"style": { "language": 23, "wrap": false }
}
}
Common language enums: PlainText=1, JavaScript=23, Python=40, TypeScript=49, Go=20, Shell=46, SQL=47, Java=22, Rust=44, C=12, CSS=17, HTML=21, Docker=19.
Callout (Feishu-specific highlight box)
Callout is a container block — create it first, then add child blocks inside.
// Step 1: Create callout as document child
{ "block_type": 19, "callout": { "background_color": 3, "border_color": 3, "emoji_id": "star" } }
// Step 2: POST .../blocks/{callout_block_id}/children
{ "children": [{ "block_type": 2, "text": { "elements": [{ "text_run": { "content": "Highlight text" } }] } }] }
Color enums: Red=1, Orange=2, Yellow=3, Green=4, Blue=5, Purple=6, Grey=7.
Divider
{ "block_type": 22, "divider": {} }
Image (two-step)
Step 1: Create placeholder block { "block_type": 27, "image": {} }
Step 2: Upload via POST /open-apis/drive/v1/medias/upload_all
- multipart/form-data: file, file_name, parent_type="docx_image", parent_node=\x3Cimage_block_id>
Text Styling
Apply styles via text_element_style in text_run:
| Property | Type | Effect |
|---|---|---|
bold |
bool | Bold |
italic |
bool | Italic |
strikethrough |
bool | Strikethrough |
underline |
bool | Underline |
inline_code |
bool | Inline code |
text_color |
int | Text color (same enum as callout colors) |
background_color |
int | Background color |
link.url |
string | Hyperlink |
Multiple text_run elements in one block = mixed styles in one paragraph.
Markdown to Block Mapping
| Markdown | block_type | JSON Key |
|---|---|---|
# H1 |
3 | heading1 |
## H2 |
4 | heading2 |
### H3 |
5 | heading3 |
| Paragraph | 2 | text |
- item |
12 | bullet |
1. item |
13 | ordered |
| Code fence | 14 | code |
> quote |
15 | quote |
- [ ] todo |
17 | todo |
--- |
22 | divider |
 |
27 | image (two-step) |
**bold** |
-- | text_element_style.bold: true |
*italic* |
-- | text_element_style.italic: true |
`code` |
-- | text_element_style.inline_code: true |
~~strike~~ |
-- | text_element_style.strikethrough: true |
[text](url) |
-- | text_element_style.link.url |
| (no MD equivalent) | 19 | callout (Feishu-specific) |
Concurrency & Ordering (Critical)
Problem: Concurrent Block creation API calls produce random ordering.
Solution A: Single Batch Request (Recommended)
Put all blocks in one children array, single API call:
{
"children": [
{ "block_type": 3, "heading1": { "elements": [{"text_run": {"content": "Title"}}] } },
{ "block_type": 2, "text": { "elements": [{"text_run": {"content": "Paragraph 1"}}] } },
{ "block_type": 22, "divider": {} },
{ "block_type": 4, "heading2": { "elements": [{"text_run": {"content": "Section 2"}}] } }
],
"index": 0
}
Solution B: Serial Writes with Index
For long content requiring multiple requests, execute serially with explicit index:
Request 1: index=0, write block A
Request 2: index=1, write block B (wait for A to succeed)
Request 3: index=2, write block C (wait for B to succeed)
Solution C: Collect-Then-Write (Recommended)
LLM outputs complete Markdown -> Conversion layer -> Single API batch write
Never let the LLM write one paragraph at a time with concurrent API calls.
Complete Write Flow
- Create document:
POST /open-apis/docx/v1/documentswith{ "folder_token": "\x3Ctoken>", "title": "Title" }-> returnsdocument_id - Build Block array: Convert full content to Block JSON
- Batch write:
POST .../documents/{doc_id}/blocks/{doc_id}/children?document_revision_id=-1with all blocks - Container blocks (optional): For Callout etc., get
block_idfrom step 3 response, then add children
Custom Callout Syntax
Since Markdown has no Callout equivalent, use this custom markup:
:::callout{color=yellow emoji=bulb}
Highlight content here.
Supports **bold**, *italic*, and lists.
:::
| Param | Values | Default | Purpose |
|---|---|---|---|
color |
red, orange, yellow, green, blue, purple, grey | yellow | Background & border |
emoji |
Any Feishu emoji_id (bulb, star, warning, fire) | bulb | Left icon |
border |
Same as color values | Same as color | Border color (override) |
Common templates:
:::callout{color=yellow emoji=bulb}
**Key Insight**: The most important takeaway
:::
:::callout{color=red emoji=warning}
**Warning**: Common misconception
:::
:::callout{color=green emoji=check}
**Action Item**: What to do next
:::
Rate Limits & Constraints
- Max blocks per batch: ~50 recommended
- Long articles: Split by H2/H3 sections, 200-500ms between batches
- Always use
document_revision_id=-1(latest version) - Token validity: ~2 hours, cache and refresh before expiry
Authentication
curl -X POST 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{ "app_id": "\x3Capp_id>", "app_secret": "\x3Capp_secret>" }'
Schema Pitfalls (Battle-tested)
- No Markdown tables in write ops — use bullet lists instead (prevents schema errors)
- No nested code blocks inside lists — Feishu schema validation is strict on nesting depth
- Callout is a container — always requires a two-step create (container first, then children)
- Each list item = separate Block — don't try to put multiple items in one block
References
- Create Blocks API: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block-children/create
- Block Data Structure: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/data-structure/block
- Convert API: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document/convert
- Extended API reference: See
FEISHU_API_HANDBOOK.mdin workspace root
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install feishu-doc-write - 安装完成后,直接呼叫该 Skill 的名称或使用
/feishu-doc-write触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
feishu-doc-write 是什么?
Feishu (Lark) Document API writing spec. Converts Markdown content to Feishu Block structures and writes to cloud docs. Handles concurrency ordering. Use when syncing articles, creating document blocks, or writing long-form content to Feishu docs. 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 890 次。
如何安装 feishu-doc-write?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install feishu-doc-write」即可一键安装,无需额外配置。
feishu-doc-write 是免费的吗?
是的,feishu-doc-write 完全免费(开源免费),可自由下载、安装和使用。
feishu-doc-write 支持哪些平台?
feishu-doc-write 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 feishu-doc-write?
由 sunnyyao2222-eng(@sunnyyao2222-eng)开发并维护,当前版本 v1.0.0。