This Skill is used to automatically write content to Feishu documents, with support for automatic Markdown format conversion.
Basic Concepts
Document Structure
A Feishu document is composed of multiple Blocks , and each block has a unique block_id. The document structure is as follows:
- Document: identified by
document_id, it is an online document - Block: the smallest building unit of a document, such as text, images, tables, etc.
- Page Block: the root block of the document, whose
block_idis the same asdocument_idSame
Get document_id
Get it from the document URL:
https://xxx.feishu.cn/docx/{document_id}
API Endpoints
1. Create a document
POST https://open.feishu.cn/open-apis/docx/v1/documents
Request body:
{
"folder_token": "string", // 可选,文档所在文件夹 token
"title": "string" // 可选,文档标题
}
Response example:
{
"code": 0,
"data": {
"document": {
"document_id": "TLLKdcpDro9ijQxA33ycNMabcef",
"revision_id": 1,
"title": "工作周报"
}
}
}
2. Create blocks (add child blocks at a specified position)
POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children
Query parameters:
document_revision_id: document version, -1 means the latest version
Request body:
{
"index": 0, // 插入位置索引(0 表示第一个)
"children": [...] // Block 数组
}
3. Create nested blocks (create multi-level blocks at once)
POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/descendant
Query parameters:
document_revision_id: document version, -1 means the latest version
Request body:
{
"index": 0,
"children_id": ["block_id_1", "block_id_2"], // 顶层子块 ID
"descendants": [...] // 所有块的详细定义
}
🚀 Quick Start
Method 1: Write directly to a Markdown file
# 设置环境变量后运行
FEISHU_APP_ID=xxx FEISHU_APP_SECRET=xxx node ./scripts/write-to-feishu.js <markdown_file> --title "文档标题"
# 示例
FEISHU_APP_ID=cli_xxx FEISHU_APP_SECRET=xxx node ./scripts/write-to-feishu.js README.md --title "项目介绍"
Method 2: Write to an existing document
FEISHU_APP_ID=xxx FEISHU_APP_SECRET=xxx node ./scripts/write-to-feishu.js content.md --doc-id <document_id>
Execution Flow
When the user needs to write to a Feishu document, execute the following steps:
1. Check credentials
Check whether the user has provided Feishu app credentials:
FEISHU_APP_ID: Feishu app IDFEISHU_APP_SECRET: Feishu app secret
If the user has not provided them, you must ask the user to obtain the credentials.
2. Prepare content
Save the content provided by the user as a temporary Markdown file, or directly use the file specified by the user.
3. Execute the write command
Use the RunCommand tool to execute:
FEISHU_APP_ID=<用户提供的ID> FEISHU_APP_SECRET=<用户提供的密钥> node ./write-to-feishu.js <文件路径> --title "<标题>"
4. Return the result
Return the generated document link to the user.
Supported Markdown Syntax
The script automatically converts Markdown into Feishu Blocks:
| Markdown Syntax | Feishu Block |
|---|---|
# 标题 |
Heading 1 |
## 标题 |
Heading 2 |
### 标题 |
Heading 3 |
- 列表 |
Unordered list |
1. 列表 |
Ordered list |
```code``` |
Code block (supports language highlighting) |
> 引用 |
Quote block |
--- |
Divider |
**粗体** |
Bold text |
*斜体* |
Italic text |
`代码` |
Inline code |
~~删除线~~ |
Strikethrough text |
- [ ] 待办 |
To-do item |
- [x] 已完成 |
Completed to-do |
| 表格 | |
Table (standard Markdown table) |
:::callout ... ::: |
Callout block (custom syntax) |
:::grid cols=N ... ::: |
Columns (custom syntax) |
Extended Syntax
Callout
:::callout color=green emoji=bulb
这是高亮块的内容,支持 **粗体** 和 _斜体_。
:::
Color options: red, orange, yellow, green, blue, purple, gray
Grid
:::grid cols=2
第一列内容
---col---
第二列内容
:::
Column limit: 2-4 columns, separated by ---col--- or :::col .
Table
Use standard Markdown table syntax:
| 列1 | 列2 | 列3 |
| --- | --- | --- |
| A1 | B1 | C1 |
| A2 | B2 | C2 |
Block Basic Structure
{
"block_id": "string",
"block_type": 2,
"parent_id": "string",
"children": ["child_block_id_1"],
"text": { ... } // 根据 block_type 对应的数据实体
}
Block Type Quick Reference
| block_type | Value | Description |
|---|---|---|
| page | 1 | Page Block |
| text | 2 | Text Block |
| heading1 | 3 | Heading 1 |
| heading2 | 4 | Heading 2 |
| heading3 | 5 | Heading 3 |
| heading4 | 6 | Heading 4 |
| heading5 | 7 | Heading 5 |
| heading6 | 8 | Heading 6 |
| heading7 | 9 | Heading 7 |
| heading8 | 10 | Heading 8 |
| heading9 | 11 | Heading 9 |
| bullet | 12 | Bulleted List |
| ordered | 13 | Numbered List |
| code | 14 | Code Block |
| quote | 15 | Quote Block |
| todo | 17 | To-do |
| divider | 22 | Divider |
| image | 27 | Image Block |
| sheet | 30 | Spreadsheet |
| table | 31 | Table Block |
| table_cell | 32 | Table Cell |
| grid | 34 | Column Block |
| grid_column | 35 | Column |
| callout | 19 | Callout Block |
| quote_container | 44 | Quote Container |
Block Structure Template
Text Block
{
"block_type": 2,
"text": {
"elements": [{ "text_run": { "content": "文本内容" } }],
"style": {
"align": 1 // 1: 左对齐, 2: 居中, 3: 右对齐
}
}
}
Styled Text
{
"block_type": 2,
"text": {
"elements": [
{
"text_run": {
"content": "粗体",
"text_element_style": { "bold": true }
}
},
{
"text_run": {
"content": "斜体",
"text_element_style": { "italic": true }
}
},
{
"text_run": {
"content": "下划线",
"text_element_style": { "underline": true }
}
}
]
}
}
More text styles:
{
"text_run": {
"content": "带样式的文本",
"text_element_style": {
"bold": true, // 粗体
"italic": true, // 斜体
"underline": true, // 下划线
"strikethrough": false, // 删除线
"inline_code": false, // 行内代码
"text_color": 1, // 文字颜色
"background_color": 3, // 背景颜色
"link": {
// 链接
"url": "https://example.com"
}
}
}
}
Heading Block
{
"block_type": 3,
"heading1": {
"elements": [{ "text_run": { "content": "标题内容" } }],
"style": {
"folded": false
}
}
}
Note:
block_typeare 3-11 respectively, corresponding to heading1-heading9 respectively.heading1key names need to followblock_typeand change accordingly:heading1,heading2, ...heading9。
Bulleted List
{
"block_type": 12,
"bullet": {
"elements": [{ "text_run": { "content": "列表项" } }]
}
}
Numbered List
{
"block_type": 13,
"ordered": {
"elements": [{ "text_run": { "content": "列表项" } }]
}
}
Code Block
{
"block_type": 14,
"code": {
"elements": [{ "text_run": { "content": "代码内容" } }],
"style": { "language": 22, "wrap": true }
}
}
Common language enum values:
| language | Value |
|---|---|
| PlainText | 1 |
| Python | 18 |
| JavaScript | 22 |
| TypeScript | 30 |
| Java | 13 |
| Go | 10 |
| Rust | 20 |
| C | 2 |
| C++ | 3 |
| Shell | 23 |
| JSON | 15 |
| YAML | 35 |
| SQL | 25 |
| HTML | 11 |
| CSS | 5 |
| Markdown | 17 |
Quote Block
{
"block_type": 15,
"quote": {
"elements": [{ "text_run": { "content": "引用内容" } }]
}
}
Divider
{
"block_type": 22,
"divider": {}
}
Callout Block (requires step-by-step creation)
The Callout Block itself cannot contain text content; its text content resides in its child blocks. When creating using nested blocks:
{
"block_id": "callout_id",
"block_type": 19,
"callout": {
"background_color": 4,
"border_color": 4,
"emoji_id": "bulb"
},
"children": ["callout_text_id"]
}
Color: red=1, orange=2, yellow=3, green=4, blue=5, purple=6, gray=7
To-do
{
"block_type": 17,
"todo": {
"elements": [{ "text_run": { "content": "待办内容" } }],
"style": { "done": false }
}
}
Table (requires step-by-step creation)
Tables need to be created through the nested block API, including table, table_cell, and content blocks. To ensure order control and create multi-level blocks at once, using the descendant API is recommended:
{
"index": 0,
"children_id": ["table_id_1"],
"descendants": [
{
"block_id": "table_id_1",
"block_type": 31,
"table": {
"property": { "row_size": 2, "column_size": 3 }
},
"children": ["cell_1", "cell_2", "cell_3", "cell_4", "cell_5", "cell_6"]
},
{
"block_id": "cell_1",
"block_type": 32,
"table_cell": {},
"children": ["cell_1_text"]
},
{
"block_id": "cell_1_text",
"block_type": 2,
"text": {
"elements": [{ "text_run": { "content": "表头1" } }]
},
"children": []
}
// ... 其他单元格
]
}
After creation, get the cell_ids, then write content into each cell, or directly use the descendants API to write text content at creation time.
Columns (requires step-by-step creation)
Columns also require the nested block API; use descendant to create child columns and column content at once:
{
"index": 0,
"children_id": ["grid_id_1"],
"descendants": [
{
"block_id": "grid_id_1",
"block_type": 34,
"grid": { "column_size": 2 },
"children": ["column_1", "column_2"]
},
{
"block_id": "column_1",
"block_type": 35,
"grid_column": { "width_ratio": 50 },
"children": ["column_1_content"]
},
{
"block_id": "column_1_content",
"block_type": 2,
"text": {
"elements": [{ "text_run": { "content": "左栏内容" } }]
},
"children": []
}
// ... 第二栏
]
}
Column count limit: 2-4 columns. After creation, get the grid_column_ids, then write content into each column, or directly use the descendants API to build the entire tree at once.
Image Block
Images require two steps:
Step 1: Upload the image to the document
POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/media
Content-Type: multipart/form-data
file: (binary)
type: "image"
Step 2: Create the image block
{
"block_type": 27,
"image": {
"token": "上传返回的 file_token",
"width": 800,
"height": 600
}
}
Ensure writing order
Method 1: Call the API sequentially
Call the create block API one by one in order, using the index parameter to specify the position each time:
async function writeBlocksInOrder(documentId, parentBlockId, blocks) {
for (let i = 0; i < blocks.length; i++) {
await createBlock(documentId, parentBlockId, {
index: i,
children: [blocks[i]],
});
}
}
Method 2: Use the nested block API (recommended)
Use the descendant API to create multiple blocks at once, controlling order through the children_id array:
{
"index": 0,
"children_id": ["block_1", "block_2", "block_3"], // 按顺序排列
"descendants": [
{ "block_id": "block_1", ... },
{ "block_id": "block_2", ... },
{ "block_id": "block_3", ... }
]
}
Method 3: Batch create blocks
When creating multiple sibling blocks in a single request, thechildren array order is the display order:
{
"index": 0,
"children": [
{ "block_type": 3, "heading1": {...} },
{ "block_type": 2, "text": {...} },
{ "block_type": 12, "bullet": {...} }
]
}
Markdown to Block Conversion Rules
When the user provides Markdown content, convert it according to the following rules:
| Markdown | Block Type |
|---|---|
# 标题 |
heading1 (3) |
## 标题 |
heading2 (4) |
### 标题 |
heading3 (5) |
#### 标题 |
heading4 (6) |
- 列表 or * 列表 |
bullet (12) |
1. 列表 |
ordered (13) |
```代码``` |
code (14) |
> 引用 |
quote (15) |
---or*** |
divider (22) |
**粗体** |
text_element_style.bold |
*斜体* |
text_element_style.italic |
`代码` |
text_element_style.inline_code |
~~删除~~ |
text_element_style.strikethrough |
- [ ] 待办 |
todo (17, done=false) |
- [x] 已完成 |
todo (17, done=true) |
| A | B | |
table (31) + table_cell (32) |
:::callout color=green |
callout (19) + text (2) |
:::grid cols=2 |
grid (24) + grid_column (25) |
Execution Example
When the user says "Help me create a Feishu document containing a project introduction":
- Ask for Feishu credentials (if not available)
- Generate a write script or directly execute the API call
- Create the document and write the content
- Return the document link to the user
Example of creating a complete document via API
const BASE_URL = "https://open.feishu.cn/open-apis";
const ACCESS_TOKEN = "your_access_token";
async function createDocWithBlocks() {
// 1. 创建文档
const docResponse = await fetch(`${BASE_URL}/docx/v1/documents`, {
method: "POST",
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ title: "示例文档" }),
});
const docData = await docResponse.json();
const documentId = docData.data.document.document_id;
// 2. 使用嵌套块 API 添加内容
const blocks = {
index: 0,
children_id: ["heading", "text", "bullet1", "code", "callout"],
descendants: [
{
block_id: "heading",
block_type: 3,
heading1: { elements: [{ text_run: { content: "文档标题" } }] },
children: [],
},
{
block_id: "text",
block_type: 2,
text: { elements: [{ text_run: { content: "这是一段正文内容。" } }] },
children: [],
},
{
block_id: "bullet1",
block_type: 12,
bullet: { elements: [{ text_run: { content: "要点一" } }] },
children: [],
},
{
block_id: "code",
block_type: 14,
code: {
elements: [{ text_run: { content: 'console.log("Hello!");' } }],
style: { language: 22, wrap: true },
},
children: [],
},
{
block_id: "callout",
block_type: 19,
callout: { background_color: 4, border_color: 4, emoji_id: "bulb" },
children: ["callout_text"],
},
{
block_id: "callout_text",
block_type: 2,
text: { elements: [{ text_run: { content: "这是高亮块中的内容" } }] },
children: [],
},
],
};
await fetch(
`${BASE_URL}/docx/v1/documents/${documentId}/blocks/${documentId}/descendant?document_revision_id=-1`,
{
method: "POST",
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(blocks),
},
);
console.log(`文档创建成功: ${documentId}`);
}
Notes
- API Limits: At least 200ms between each call to avoid 429 errors
- Permission Requirements: The application requires
docx:documentpermission and needs to obtaintenant_access_tokenoruser_access_token - Nested Blocks: Highlight blocks, tables, etc. need to be created step by step. When using the nested block API, the custom
block_idmust be unique within the document - Parent-Child Relationship Rules:
- Only specific block types can serve as parent blocks (such as Page, Text, TableCell, etc.)
- Page blocks cannot be added as child blocks
- TableCell can only be added through table operations
- Table Limits:
- Blocks such as Table and Sheet are not allowed to be added inside table cells
- Grid blocks are not allowed to be added inside column containers
- Version Control: It is recommended to use
document_revision_id=-1to always operate on the latest version - Error Handling: Pay attention to handling common error codes such as the 400177xxx series