lark_docx
Read and write Feishu/Lark Docs (云文档, docx).
Prerequisites
- App scope: the Lark app needs the
docx:documentpermission (plusdrive:driveif you wantcreateto place documents in a folder) - Collaborator: the bot must be added as a collaborator of the document.
Without this the API answers
91403/1254302. - Credentials:
integrations.lark.app_idandapp_secretin your config. The tool shares the tenant token cache with the Lark IM integration,bitableandlark_sheets.
require('chat').setup({
integrations = {
lark = {
app_id = 'cli_xxxxxxxxxxxx',
app_secret = 'xxxxxxxxxxxxxxxx',
},
},
})
Usage
@lark_docx action="<action>" [document_id="<doxcn...>"] [url="<link>"] [block_id="<block>"]
Actions
| Action | Method | Required | Description |
|---|---|---|---|
get |
GET | document_id or url |
Document metadata (title, revision) |
read |
GET | document_id or url |
Flatten the document into markdown |
append |
POST | token or url, plus text/blocks |
Add blocks at the end |
update_block |
PATCH | token or url, block_id, block |
Replace one block |
delete_block |
DELETE | token or url, block_id |
Remove one block and its children |
create |
POST | title |
Create an empty document in Drive |
action defaults to read.
Parameters
| Parameter | Type | Description |
|---|---|---|
action |
string | One of the actions above (default: read) |
document_id |
string | Document id, the doxcn... segment of the doc URL |
url |
string | Document link (or a wiki link holding one) — alternative to the id |
block_id |
string | Block for update_block / delete_block; for append it selects the parent block (default: the document root) |
text |
string | Plain text to append, converted to blocks locally |
blocks |
array | Raw docx block objects to append (advanced) |
block |
object | Replacement block object (update_block) |
index |
integer | Insertion position among the parent’s children (default: 0 = end) |
page_size |
integer | Blocks per page for read, max 500 (default: 500) |
page_token |
string | Pagination cursor from a previous read |
title |
string | Title of the document to create (create, required) |
folder_token |
string | Folder to create the document in (create, optional) |
Appending content
Two ways in, chosen by which parameter you pass.
text — recommended
Plain text is converted into docx blocks locally, so you never have to build
the nested elements structure by hand:
| Line looks like | Becomes |
|---|---|
# Title |
heading1 (block_type 3) |
## Sub |
heading2 (block_type 4) |
… up to ###### |
heading6 (block_type 8) |
- item or * item |
bullet (block_type 12) |
1. item |
ordered item (block_type 13) |
| anything else | plain text (block_type 2) |
| blank line | skipped |
@lark_docx action="append" document_id="doxcnABC" text="# Report\n\n- finding one\n- finding two\n1. next step"
Notes:
- Seven or more
#is not a heading level — the line falls back to plain text. 2)is not a recognised ordered marker; onlyN.is.- Blank lines are skipped rather than emitted as empty blocks, to keep the document tight.
- Nesting is intentionally flattened: docx indent properties differ from
markdown, and guessing them silently produces wrong-looking documents. Use
blockswhen you need real nesting.
blocks — raw control
The array is forwarded untouched as the request body’s children:
[{"block_type":2,"text":{"elements":[{"text_run":{"content":"hello"}}]}}]
block_type |
Meaning |
|---|---|
| 1 | page (document root) |
| 2 | text |
| 3 .. 11 | heading1 .. heading9 |
| 12 | bullet |
| 13 | ordered |
Every text-ish block wraps its string in elements → text_run → content,
and the payload key mirrors the type (text, heading1, bullet,
ordered). A missing text_run wrapper is the single most common cause of a
rejected request.
Reading
read rebuilds the block tree from the flat parent_id / children links and
renders it as markdown with headings and list markers (list children indent
one level). Inline styling, tables and images are flattened to their text.
- When the output ends with
[... document continues; call read again with page_token="..."], pass that token back for the next page. - If the page’s blocks have no usable tree links (their parent sits on a previous page), the API order is used as the fallback.
Each page is at most 500 blocks. Very long documents therefore take several calls.
Where to find tokens
document_id: thedoxcn...segment of the doc URL (https://xxx.feishu.cn/docx/doxcnXXXX). It is not a wikinode_token.block_id: fromreadoutput or the doc URL fragment. The document root block id equalsdocument_id, which is whatappenduses when noblock_idis given.url: paste the link instead. A/wiki/wikcn...link is resolved automatically (one extra request), but only when the wiki page actually holds a document.
Examples
# document metadata
@lark_docx action="get" document_id="doxcnABC123"
# read the whole document as markdown
@lark_docx action="read" document_id="doxcnABC123"
# read, pasting the link (wiki links resolve automatically)
@lark_docx action="read" url="https://xxx.feishu.cn/docx/doxcnABC123"
# append markdown-ish text at the end
@lark_docx action="append" document_id="doxcnABC123" text="# Summary\n- point one\n- point two"
# append raw blocks
@lark_docx action="append" document_id="doxcnABC123" blocks=[{"block_type":2,"text":{"elements":[{"text_run":{"content":"hi"}}]}}]
# append under a specific block
@lark_docx action="append" document_id="doxcnABC123" block_id="blkParent" text="nested line"
# replace one block
@lark_docx action="update_block" document_id="doxcnABC123" block_id="blk1" block={"block_type":2,"text":{"elements":[{"text_run":{"content":"new"}}]}}
# remove one block and its children
@lark_docx action="delete_block" document_id="doxcnABC123" block_id="blk1"
# create a new document
@lark_docx action="create" title="Q1 Report"
Notes
- Requires
curlto be installed and available in PATH- Request bodies are piped over stdin, so document content never appears in the process command line
- Cancelling while the tenant token (or a wiki node) is still being fetched aborts the call without firing the request — a cancelled append never touches your document
createreturns the new document’sdocument_id, which is thedocument_idfor every later calldelete_blockremoves the block and all of its children, and cannot be undone
Troubleshooting
| Error | Cause |
|---|---|
91403 / 1254302 |
The bot is not a collaborator of the document |
1254005 |
The app is missing the docx:document scope |
1770001 / invalid block |
A text block is missing the text_run wrapper |
Invalid JSON |
Network/proxy returned a non-JSON page (check log_id) |