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.
CODEBLOCK0
Preferred Approach: Convert API
Feishu provides an official Markdown -> Blocks conversion endpoint:
CODEBLOCK1
CODEBLOCK2
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 | INLINECODE0 | 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
CODEBLOCK3
- -
block_id: Parent block ID (usually document_id itself for root) - INLINECODE18 : Insert position (0 = beginning, -1 or omit = end)
Block JSON Examples
Text
CODEBLOCK4
Heading
CODEBLOCK5
Bullet / Ordered List
CODEBLOCK6
Each list item is a separate Block.
Code Block
CODEBLOCK7
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.
CODEBLOCK8
Color enums: Red=1, Orange=2, Yellow=3, Green=4, Blue=5, Purple=6, Grey=7.
Divider
CODEBLOCK9
Image (two-step)
CODEBLOCK10
Text Styling
Apply styles via text_element_style in text_run:
| Property | Type | Effect |
|---|
| INLINECODE21 | bool | Bold |
| INLINECODE22 |
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 |
|---|
| INLINECODE30 | 3 | INLINECODE31 |
| INLINECODE32 |
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
elementstyle.inline
code: true |
| ~~strike~~ | -- | textelement
style.strikethrough: true |
| text | -- | textelementstyle.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:
CODEBLOCK11
### Solution B: Serial Writes with Index
For long content requiring multiple requests, execute **serially** with explicit index:
CODEBLOCK12
### Solution C: Collect-Then-Write (Recommended)
CODEBLOCK13
**Never** let the LLM write one paragraph at a time with concurrent API calls.
## Complete Write Flow
1. **Create document**: POST /open-apis/docx/v1/documents with { "foldertoken": "", "title": "Title" } -> returns documentid
2. **Build Block array**: Convert full content to Block JSON
3. **Batch write**: POST .../documents/{docid}/blocks/{docid}/children?documentrevisionid=-1 with all blocks
4. **Container blocks** (optional): For Callout etc., get blockid from step 3 response, then add children
## Custom Callout Syntax
Since Markdown has no Callout equivalent, use this custom markup:
CODEBLOCK14
| 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:
CODEBLOCK15
## Rate Limits & Constraints
- Max blocks per batch: ~50 recommended
- Long articles: Split by H2/H3 sections, 200-500ms between batches
- Always use documentrevisionid=-1 (latest version)
- Token validity: ~2 hours, cache and refresh before expiry
## Authentication
CODEBLOCK16
## 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 FEISHUAPI_HANDBOOK.md` in workspace root
飞书文档写入器
通过 Docx API 向飞书云文档写入内容的参考规范。飞书文档采用块树模型——不支持原始 Markdown。
文档(block_type=1, Page)
+-- 标题1块(block_type=3)
+-- 文本块(block_type=2)
+-- 高亮块(block_type=19)
| +-- 文本块
| +-- 无序列表块
+-- 图片块(block_type=27)
+-- 分割线块(block_type=22)
推荐方案:转换接口
飞书提供了官方的 Markdown -> 块 转换接口:
POST /open-apis/docx/v1/documents/{document_id}/convert
json
{
content: # 标题\n\n正文内容\n\n- 项目1\n- 项目2\n\n> 引用,
content_type: markdown
}
优点:无需手动构建块 JSON。支持大多数标准 Markdown。
限制:不支持飞书特有的块(如高亮块等)——这些需要使用手动创建块的方式。
块类型参考
| block_type | 名称 | JSON 键 | 说明 |
|---|
| 1 | 页面 | page | 文档根节点 |
| 2 |
文本 | text | 段落 |
| 3-11 | 标题1-9 | heading1-heading9 | 标题 |
| 12 | 无序列表 | bullet | 无序列表(每个项目为独立块) |
| 13 | 有序列表 | ordered | 有序列表 |
| 14 | 代码 | code | 代码块(含 style.language 枚举) |
| 15 | 引用 | quote | 块引用 |
| 17 | 待办 | todo | 复选框项目(含 style.done) |
| 19 | 高亮块 | callout | 高亮框(飞书特有,容器块) |
| 22 | 分割线 | divider | 水平分割线 |
| 27 | 图片 | image | 两步操作:先创建占位,再上传 |
| 31 | 表格 | table | 表格 |
| 34 | 引用容器 | quote_container | 引用容器 |
创建块接口
POST /open-apis/docx/v1/documents/{documentid}/blocks/{blockid}/children?documentrevisionid=-1
请求头:
Content-Type: application/json
Authorization: Bearer accesstoken>
请求体:
{
children: [ ...块数组... ],
index: 0
}
- - blockid:父块 ID(根节点通常为 documentid 本身)
- index:插入位置(0 = 开头,-1 或省略 = 末尾)
块 JSON 示例
文本
json
{
block_type: 2,
text: {
elements: [{
text_run: {
content: 此处为段落文本,
textelementstyle: { bold: false, italic: false }
}
}]
}
}
标题
json
{ blocktype: 3, heading1: { elements: [{ textrun: { content: H1 标题 } }] } }
{ blocktype: 4, heading2: { elements: [{ textrun: { content: H2 标题 } }] } }
无序列表 / 有序列表
json
{ blocktype: 12, bullet: { elements: [{ textrun: { content: 列表项目 } }] } }
{ blocktype: 13, ordered: { elements: [{ textrun: { content: 编号项目 } }] } }
每个列表项是一个独立的块。
代码块
json
{
block_type: 14,
code: {
elements: [{ text_run: { content: console.log(hello); } }],
style: { language: 23, wrap: false }
}
}
常用语言枚举:纯文本=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。
高亮块(飞书特有的高亮框)
高亮块是一个容器块——先创建它,然后在内部添加子块。
json
// 步骤1:创建高亮块作为文档子块
{ blocktype: 19, callout: { backgroundcolor: 3, bordercolor: 3, emojiid: star } }
// 步骤2:POST .../blocks/{calloutblockid}/children
{ children: [{ blocktype: 2, text: { elements: [{ textrun: { content: 高亮文本 } }] } }] }
颜色枚举:红色=1, 橙色=2, 黄色=3, 绿色=4, 蓝色=5, 紫色=6, 灰色=7。
分割线
json
{ block_type: 22, divider: {} }
图片(两步操作)
步骤1:创建占位块 { block_type: 27, image: {} }
步骤2:通过 POST /open-apis/drive/v1/medias/upload_all 上传
- multipart/form-data: file, filename, parenttype=docximage, parentnode=blockid>
文本样式
通过 textrun 中的 textelement_style 应用样式:
布尔值 | 斜体 |
| strikethrough | 布尔值 | 删除线 |
| underline | 布尔值 | 下划线 |
| inline_code | 布尔值 | 行内代码 |
| text_color | 整数 | 文本颜色(与高亮块颜色枚举相同) |
| background_color | 整数 | 背景颜色 |
| link.url | 字符串 | 超链接 |
一个块中包含多个 text_run 元素 = 同一段落中的混合样式。
Markdown 到块的映射
| Markdown | block_type | JSON 键 |
|---|
| # H1 | 3 | heading1 |
| ## H2 |
4 | heading2 |
| ### H3 | 5 | heading3 |
| 段落 | 2 | text |
| - 项目 | 12 | bullet |
| 1. 项目 | 13 | ordered |
| 代码围栏 | 14 | code |
| > 引用 | 15 | quote |
| - [ ] 待办 | 17 | todo |
| --- | 22 | divider |
|

| 27 | image(两步操作) |
|
加粗 | -- | text
elementstyle.bold: true |
|
斜体 | -- | text
elementstyle.italic: true |
| 代码 | -- | text
elementstyle.inline_code: true |
| ~~删除~~ | -- | text
elementstyle.strikethrough: true |
|
文本 | -- | textelement_style.link.url |
| (无 MD 等价物) | 19 | callout(飞书特有) |
并发与排序(关键)
问题:并发创建块的 API 调用会产生随机排序。
方案 A:单次批量请求(推荐)
将所有块放在一个 children 数组中,单次 API 调用:
json
{
children: [
{ blocktype: 3, heading1: { elements: [{textrun: {content: 标题}}] } },
{ blocktype: 2, text: { elements: [{textrun: {content: 段落1}}] } },
{ block_type: 22, divider: {} },
{ blocktype: 4, heading2: { elements: [{textrun: {content: 章节2}}] } }
],
index: 0
}
方案 B:带索引的串行写入
对于需要多次请求的长内容