| name | openclaw-lark-integration |
| description | Official Lark/Feishu plugin for OpenClaw that enables AI agents to interact with Lark workspaces including messages, docs, bases, calendars, and tasks |
| triggers | ["integrate OpenClaw with Lark","set up Feishu plugin for OpenClaw","connect my AI agent to Lark workspace","configure OpenClaw Lark permissions","create a Lark bot with OpenClaw","troubleshoot OpenClaw Lark integration","manage OpenClaw Lark group settings","use OpenClaw with Feishu documents"] |
OpenClaw Lark Integration
Skill by ara.so — Hermes Skills collection.
Official Lark/Feishu plugin for OpenClaw that seamlessly connects AI agents to Lark workspaces. Enables reading and writing messages, managing docs, bases, sheets, calendars, and tasks with built-in security controls and interactive cards.
What It Does
The OpenClaw Lark plugin allows AI agents to:
- Messaging: Read message history (groups/DMs/threads), send/reply to messages, search, download attachments
- Documents: Create, update, and read Lark docs
- Base: Full CRUD operations on bases, tables, fields, records with advanced filtering
- Sheets: Create, edit, and view spreadsheets
- Calendar: Manage calendars, events, attendees, and check free/busy status
- Tasks: Create, query, update, complete tasks and manage subtasks/comments
- Interactive Cards: Real-time status updates with streaming responses
- Security: Built-in permission policies and per-group configuration
Installation
Prerequisites
- Node.js v22 or higher
- OpenClaw version 2026.2.26 or higher
Check OpenClaw version:
openclaw -v
If below required version, upgrade:
npm install -g openclaw
Install Plugin
npm install -g @larksuite/openclaw-lark
Or with pnpm:
pnpm add -g @larksuite/openclaw-lark
Configuration
1. Create Lark/Feishu App
- Go to Lark Open Platform or Feishu Open Platform
- Create a new custom app
- Configure bot capabilities and permissions
- Obtain App ID and App Secret
2. Required Permissions
The app needs these permission scopes:
Messaging:
im:message - Send messages
im:message:read_as_user - Read messages as user
im:chat - Access chat information
Documents:
docx:document - Manage docs
drive:drive - Access drive files
Base:
bitable:app - Manage base apps
bitable:record - Manage records
Sheets:
sheets:spreadsheet - Manage spreadsheets
Calendar:
calendar:calendar - Manage calendars
calendar:event - Manage events
Tasks:
3. Configure Environment Variables
Create or update your OpenClaw config file with Lark credentials:
export default {
channels: {
lark: {
appId: process.env.LARK_APP_ID,
appSecret: process.env.LARK_APP_SECRET,
verificationToken: process.env.LARK_VERIFICATION_TOKEN,
encryptKey: process.env.LARK_ENCRYPT_KEY,
policies: {
allowPrivateChat: true,
allowGroupChat: false,
groupAllowlist: [],
},
enableInteractiveCards: true,
enableStreamingResponse: true,
}
}
}
Set environment variables:
export LARK_APP_ID="your_app_id"
export LARK_APP_SECRET="your_app_secret"
export LARK_VERIFICATION_TOKEN="your_verification_token"
export LARK_ENCRYPT_KEY="your_encrypt_key"
4. Start OpenClaw with Lark Channel
openclaw start --channel lark
Or specify config file:
openclaw start --config openclaw.config.ts
Key Commands
OpenClaw CLI Commands
openclaw start --channel lark
openclaw -v
openclaw --help
openclaw stop
Managing the Bot
Once running, interact with your bot in Lark/Feishu by:
- Sending direct messages
- @mentioning in allowed groups
- Using configured skills and prompts
API Usage Patterns
Sending Messages
The plugin automatically handles message sending through OpenClaw's unified interface:
import { SkillContext } from 'openclaw';
export async function sendLarkMessage(context: SkillContext, message: string) {
await context.channel.sendMessage({
text: message,
chatId: context.chatId
});
}
Reading Messages
export async function getRecentMessages(context: SkillContext, limit: number = 10) {
const messages = await context.channel.getMessages({
chatId: context.chatId,
limit: limit
});
return messages.map(msg => ({
sender: msg.sender,
content: msg.content,
timestamp: msg.timestamp
}));
}
Working with Documents
export async function createDocument(
context: SkillContext,
title: string,
content: string
) {
const doc = await context.channel.lark.createDoc({
title: title,
content: content,
folderToken: context.workspace.defaultFolder
});
return {
docId: doc.docToken,
url: doc.url
};
}
Managing Base Records
export async function addBaseRecord(
context: SkillContext,
baseId: string,
tableId: string,
fields: Record<string, any>
) {
const record = await context.channel.lark.base.createRecord({
appToken: baseId,
tableId: tableId,
fields: fields
});
return record;
}
export async function queryBaseRecords(
context: SkillContext,
baseId: string,
tableId: string,
filter?: string
) {
const records = await context.channel.lark.base.listRecords({
appToken: baseId,
tableId: tableId,
filter: filter,
pageSize: 100
});
return records.items;
}
Calendar Operations
export async function createCalendarEvent(
context: SkillContext,
summary: string,
startTime: string,
endTime: string,
attendees?: string[]
) {
const event = await context.channel.lark.calendar.createEvent({
summary: summary,
startTime: { timestamp: startTime },
endTime: { timestamp: endTime },
attendees: attendees?.map(email => ({ email }))
});
return {
eventId: event.eventId,
htmlLink: event.htmlLink
};
}
Task Management
export async function createTask(
context: SkillContext,
summary: string,
description: string,
dueDate?: string
) {
const task = await context.channel.lark.task.createTask({
summary: summary,
description: description,
due: dueDate ? { date: dueDate } : undefined
});
return task;
}
Security Configuration
Permission Policies
Configure access control in your config file:
export default {
channels: {
lark: {
policies: {
allowPrivateChat: true,
allowGroupChat: false,
allowGroupChat: true,
groupAllowlist: [
'oc_xxxxxxxxxxxxx',
'oc_yyyyyyyyyyyyy'
],
requireConfirmation: {
deleteDocument: true,
deleteBaseRecord: true,
sendMessageToGroup: true
}
}
}
}
}
Per-Group Configuration
export default {
channels: {
lark: {
groupSettings: {
'oc_xxxxxxxxxxxxx': {
enabled: true,
allowedSkills: ['search', 'summarize'],
customSystemPrompt: 'You are a helpful assistant for the engineering team.',
maxTokens: 4000
}
}
}
}
}
Interactive Cards and Streaming
Enable Streaming Responses
export default {
channels: {
lark: {
enableStreamingResponse: true,
enableInteractiveCards: true
}
}
}
Streaming automatically shows:
- 🤔 Thinking indicator
- 📝 Generating status with live text
- ✅ Complete notification
Custom Interactive Cards
export async function sendCardWithActions(context: SkillContext) {
await context.channel.sendCard({
header: {
title: 'Confirm Action',
template: 'blue'
},
elements: [
{
tag: 'div',
text: {
tag: 'plain_text',
content: 'Do you want to proceed with this operation?'
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: 'Confirm' },
type: 'primary',
value: { action: 'confirm' }
},
{
tag: 'button',
text: { tag: 'plain_text', content: 'Cancel' },
type: 'default',
value: { action: 'cancel' }
}
]
}
]
});
}
Common Patterns
Message Handler Skill
import { Skill } from 'openclaw';
export const messageHandlerSkill: Skill = {
name: 'lark-message-handler',
description: 'Handle incoming Lark messages',
async execute(context) {
const { message, sender } = context;
if (message.includes('help')) {
return await context.reply('How can I assist you?');
}
if (message.startsWith('search:')) {
const query = message.substring(7);
const results = await context.channel.searchMessages({ query });
return results;
}
return await context.reply('Message received');
}
};
Document Automation
export async function createWeeklyReport(context: SkillContext) {
const today = new Date();
const title = `Weekly Report - ${today.toISOString().split('T')[0]}`;
const tasks = await context.channel.lark.base.listRecords({
appToken: process.env.LARK_TASK_BASE_ID!,
tableId: 'tblxxxxxxxx',
filter: "AND(CurrentValue.[CompletedAt] >= DATE_SUB(TODAY(), 7))"
});
const content = `
# ${title}
## Completed Tasks
${tasks.items.map(t => `- ${t.fields.Name}`).join('\n')}
## Summary
Total tasks completed: ${tasks.items.length}
`;
const doc = await context.channel.lark.createDoc({
title,
content
});
return doc.url;
}
Batch Operations
export async function batchUpdateRecords(
context: SkillContext,
baseId: string,
tableId: string,
updates: Array<{ recordId: string; fields: Record<string, any> }>
) {
const result = await context.channel.lark.base.batchUpdateRecords({
appToken: baseId,
tableId: tableId,
records: updates
});
return {
updated: result.records.length,
records: result.records
};
}
Troubleshooting
Bot Not Responding
Issue: Bot doesn't reply to messages
Solutions:
- Verify OpenClaw is running:
openclaw status
- Check credentials are set correctly
- Ensure bot has required permissions in Lark admin console
- Verify group is in allowlist if
allowGroupChat is true
- Check logs:
openclaw logs --channel lark
Permission Errors
Issue: "Permission denied" errors
Solutions:
- Review required scopes in Lark app settings
- Re-authorize the app after adding permissions
- Check if user has necessary workspace permissions
- Verify app is published (not in development mode)
Message Not Sent
Issue: Messages fail to send
Solutions:
- Check if chat ID is valid
- Verify bot is added to the group chat
- Ensure
im:message permission is granted
- Check message format and size limits
Configuration Not Loading
Issue: Config changes not taking effect
Solutions:
- Restart OpenClaw after config changes
- Verify config file path:
openclaw start --config ./path/to/config.ts
- Check for TypeScript/JSON syntax errors
- Ensure environment variables are exported
Streaming Not Working
Issue: Streaming responses not appearing
Solutions:
- Verify
enableStreamingResponse: true in config
- Check Lark client version supports interactive cards
- Test with simple message first
- Review network/firewall settings
Rate Limiting
Issue: "Rate limit exceeded" errors
Solutions:
- Implement exponential backoff in custom skills
- Cache frequently accessed data
- Use batch operations where possible
- Monitor API quota in Lark admin console
Best Practices
- Security First: Never disable default security policies without understanding risks
- Use Private Chats: Recommended for personal assistants to avoid permission abuse
- Whitelist Groups: If using in groups, explicitly whitelist trusted chats only
- Environment Variables: Always use env vars for credentials, never hardcode
- Error Handling: Wrap API calls in try-catch blocks
- Confirmation Dialogs: Use interactive cards for destructive operations
- Rate Limiting: Respect API limits, implement backoff strategies
- Logging: Enable detailed logs during development for debugging
- Skill Restrictions: Limit available skills per group to reduce attack surface
- Regular Updates: Keep OpenClaw and plugin updated for security patches
Additional Resources