feishu-card (schema 2.0)
Schlüsselregeln
- Alle Karten verwenden Schema 2.0, Elemente werden in
body.elements[]platziert, nicht auf oberster Ebeneelements[] - Karten müssen mit
--as bot,--msg-type interactiveund--contentals JSON-String gesendet werden - Erwähnungen in Karten verwenden
<at id="ou_xxx">Name</at>(nicht<at user_id>) - JSON 2.0 Buttons haben keinen
action-Container mehr, Buttons werden direkt inbody.elementsplatziert, Interaktionen erfolgen über dasbehaviors-Array - Zuerst eine "Denken"-Karte senden, um die
message_idzu erhalten, nach Claudes Antwort mit PATCH lokal aktualisieren, um Nachrichtenflut zu vermeiden - Der
note-Tag wird in Schema 2.0 nicht mehr unterstützt (Fehler 200861), für Notizen stattdessendiv + lark_mdverwenden (<font color=grey>Notiztext</font>) - Das JSON der Karte muss mit Python heredoc oder einer Datei erstellt werden,
$(python3 -c "...")ist verboten – bei Inhalten mit Backticks oder Sonderzeichen führt Bash zu Syntaxfehlern
Vollständige Felder der obersten Ebene
{
"schema": "2.0",
"config": {
"streaming_mode": false,
"enable_forward": true,
"update_multi": true,
"width_mode": "default"
},
"card_link": {
"url": "https://...",
"pc_url": "https://...",
"ios_url": "https://...",
"android_url": "https://..."
},
"header": { "...": "..." },
"body": {
"direction": "vertical",
"padding": "0px",
"elements": []
}
}
config-Felder
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
streaming_mode | bool | false | Streaming-Update-Modus (für Platzhalter während des Denkens) |
enable_forward | bool | true | Erlaubt das Weiterleiten der Karte |
update_multi | bool | false | JSON 2.0 Shared-Card-Modus (für synchrone Updates mehrerer Benutzer auf true setzen) |
width_mode | string | default (600px) | compact (400px) / fill (volle Breite, responsiv) / default |
Header-Felder
{
"header": {
"title": {"tag": "plain_text", "content": "Titel"},
"subtitle": {"tag": "plain_text", "content": "Untertitel (optional)"},
"template": "blue",
"icon": {
"tag": "standard_icon",
"token": "robot-outlined",
"color": "blue"
},
"text_tag_list": [
{"tag": "text_tag", "text": {"tag": "plain_text", "content": "Neu"}, "color": "blue"}
]
}
}
template-Farben: blue / wathet / turquoise / green / yellow / orange / red / carmine / violet / purple / indigo / grey
Feishu-Farben-Schnellreferenz
Alle Felder, die Farben unterstützen (background_color, text_color, icon color, usw.), verwenden das folgende Format. Es dürfen nicht nur die Basis-Farbnamen verwendet werden (z. B. führt "blue" bei einigen Komponenten zu Fehlern). Es muss ein Ebenen-Suffix angegeben werden:
| Farbsystem | Hell (für Hintergründe empfohlen) | Mittel | Dunkel (für Text empfohlen) |
|---|---|---|---|
| Blau blue | blue-50 blue-100 blue-200 | blue-300~blue-500 | blue-600~blue-900 |
| Wasserblau wathet | wathet-50 wathet-100 wathet-200 | wathet-300~wathet-500 | wathet-600~wathet-900 |
| Türkis turquoise | turquoise-50 turquoise-100 | turquoise-300~turquoise-500 | turquoise-600~turquoise-900 |
| Grün green | green-50 green-100 green-200 | green-300~green-500 | green-600~green-900 |
| Gelbgrün lime | lime-50 lime-100 | lime-300~lime-500 | lime-600~lime-900 |
| Gelb yellow | yellow-50 yellow-100 | yellow-300~yellow-500 | yellow-600~yellow-900 |
| Orange orange | orange-50 orange-100 | orange-300~orange-500 | orange-600~orange-900 |
| Rot red | red-50 red-100 | red-300~red-500 | red-600~red-900 |
| Pink carmine | carmine-50 carmine-100 | carmine-300~carmine-500 | carmine-600~carmine-900 |
| Lila purple | purple-50 purple-100 | purple-300~purple-500 | purple-600~purple-900 |
| Blaulila violet | violet-50 violet-100 | violet-300~violet-500 | violet-600~violet-900 |
| Indigo indigo | indigo-50 indigo-100 | indigo-300~indigo-500 | indigo-600~indigo-900 |
| Grau grey | grey-00 grey-50 grey-100 | grey-300~grey-500 | grey-600~grey-1000 |
| Weiß | white bg-white | — | — |
Empfohlene helle Hintergründe (für collapsible_panel, column-Hintergründe usw.):
blue-100 wathet-100 green-100 yellow-100
orange-100 red-100 purple-100 carmine-100
turquoise-100 violet-100 indigo-100 lime-100
Komponenten-Schnellreferenz
| tag | Typ | Beschreibung |
|---|---|---|
div | Inhalt | Textblock (plain_text / lark_md), unterstützt vorangestelltes Icon |
markdown | Inhalt | Reiner Markdown-Text (kein @, @ mit div+lark_md) |
img | Inhalt | Bild |
table | Inhalt | Datentabelle (max. 5 pro Karte, nicht verschachtelbar) |
hr | Layout | Trennlinie |
note | Layout | ❌ In Schema 2.0 veraltet, stattdessen div + lark_md + <font color=grey> verwenden |
column_set | Container | Mehrspaltiges Layout |
collapsible_panel | Container | Aufklappbares Panel |
button | Interaktion | Schaltfläche (JSON 2.0 direkt in elements, kein action-Container) |
input | Interaktion | Texteingabefeld (muss in einem form verwendet werden) |
select_static | Interaktion | Statischer Dropdown-Selektor |
div (Textblock)
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**Fett** _Kursiv_ `Code`",
"text_size": "normal",
"text_color": "default",
"text_align": "left",
"lines": 3
},
"icon": {"tag": "standard_icon", "token": "robot-outlined", "color": "blue"}
}
text.tag: plain_text (Klartext) oder lark_md (Markdown + @)
text_size: heading-0 / heading / normal (Standard) / notation / x-small
text_align: left / center / right
img (Bild)
{
"tag": "img",
"img_key": "img_xxx",
"alt": {"tag": "plain_text", "content": "Hover-Beschreibung"},
"title": {"tag": "plain_text", "content": "Bildtitel"},
"scale_type": "crop_center",
"size": "medium",
"corner_radius": "4px",
"preview": true
}
scale_type: crop_center / crop_top / fit_horizontal
size: stretch (volle Breite) / large / medium / small / tiny / "300px 200px" (benutzerdefiniert)
JSON 2.0 unterstützt
stretch_without_paddingnicht, für volle Breite stattdessen negativen Margin verwenden
column_set (Mehrspaltiges Layout)
{
"tag": "column_set",
"flex_mode": "bisect",
"horizontal_spacing": "medium",
"background_style": "default",
"columns": [
{
"tag": "column",
"width": "weighted",
"weight": 1,
"vertical_align": "top",
"elements": [{"tag": "div", "text": {"tag": "plain_text", "content": "Linke Spalte"}}]
},
{
"tag": "column",
"width": "weighted",
"weight": 1,
"elements": [{"tag": "div", "text": {"tag": "plain_text", "content": "Rechte Spalte"}}]
}
]
}
flex_mode: none (proportional komprimieren) / stretch (auf schmalen Bildschirmen vertikal anordnen) / flow (automatischer Umbruch) / bisect (zwei gleich breite Spalten) / trisect (drei gleich breite Spalten)
horizontal_spacing: small(4px) / medium(8px) / large(12px) / extra_large(16px)
column.width: auto / weighted (mit weight 1-5) / "200px"
Maximal 5 Ebenen Verschachtelung, innerhalb einer Spalte dürfen kein form-Container und keine table platziert werden
collapsible_panel (Aufklappbares Panel)
{
"tag": "collapsible_panel",
"expanded": false,
"background_color": "default",
"header": {
"title": {"tag": "plain_text", "content": "Zum Aufklappen klicken"},
"background_color": "grey",
"icon": {"tag": "standard_icon", "token": "arrow-down-outlined"},
"icon_expanded_angle": -180
},
"elements": [
{"tag": "div", "text": {"tag": "plain_text", "content": "Aufgeklappter Inhalt"}}
]
}
table (Datentabelle)
{
"tag": "table",
"page_size": 5,
"row_height": "low",
"freeze_first_column": false,
"header_style": {
"text_align": "left",
"text_size": "normal",
"background_style": "grey",
"bold": true
},
"columns": [
{"name": "name", "display_name": "Name", "data_type": "text", "width": "auto"},
{"name": "status", "display_name": "Status", "data_type": "options", "width": "120px"},
{"name": "count", "display_name": "Anzahl", "data_type": "number",
"format": {"precision": 0, "separator": true}},
{"name": "ts", "display_name": "Zeit", "data_type": "date",
"format": "YYYY/MM/DD HH:mm"}
],
"rows": [
{"name": "Max Mustermann", "status": [{"name": "In Bearbeitung", "color": "blue"}], "count": 42, "ts": 1700000000000}
]
}
data_type: text / lark_md / markdown / options / number / persons / date
row_height: low / middle / high / auto / "48px" (32-124px)
Maximal 5 Tabellen pro Karte, nicht verschachtelbar
button (JSON 2.0)
{
"tag": "button",
"text": {"tag": "plain_text", "content": "Bestätigen"},
"type": "primary",
"size": "medium",
"width": "default",
"disabled": false,
"behaviors": [
{
"type": "callback",
"value": {"action": "confirm", "data": "payload"}
}
],
"confirm": {
"title": {"tag": "plain_text", "content": "Zweite Bestätigung"},
"text": {"tag": "plain_text", "content": "Möchten Sie diesen Vorgang wirklich ausführen?"}
}
}
type: default (grau) / primary (blau) / danger (rot) / text / primary_text / danger_text / primary_filled / danger_filled / laser
size: tiny / small / medium / large
behaviors[].type: callback (Ereignis auslösen) / open_url (Link öffnen)
JSON 2.0 Änderung: Es gibt keinen
action-Container mehr, Buttons werden direkt inbody.elementsodercolumn.elementsplatziert; Interaktionen erfolgen überbehaviorsanstelle des altenvalue
input (Texteingabe)
{
"tag": "input",
"name": "field_name",
"placeholder": {"tag": "plain_text", "content": "Bitte eingeben..."},
"default_value": "",
"input_type": "text",
"max_length": 200,
"required": false,
"disabled": false,
"label": {"tag": "plain_text", "content": "Beschriftung"},
"label_position": "top",
"width": "fill"
}
input_type: text / multiline_text / password
Der Benutzer löst beim Absenden das Ereignis card.action.trigger aus, das Ereignis enthält input_value
@-Erwähnungs-Syntax
| Nachrichtentyp | @-Person-Syntax | @-Alle |
|---|---|---|
Karte interactive lark_md | <at id="ou_xxx">Name</at> | <at id="all">Alle</at> |
Text text / post | <at user_id="ou_xxx">Name</at> | <at user_id="all"></at> |
@-Erwähnung innerhalb einer Karte – Verifizierte Elementstruktur (div + lark_md, kein nackter markdown-Tag):
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "<at id=\"ou_xxx\">Name</at> Nachrichtentext"
}
}
Markdown-Syntax-Schnellreferenz (lark_md / markdown-Tag)
Textstile
**Fett** *Kursiv* ~~Durchgestrichen~~ `Inline-Code`
Farbiger Text
<font color=red>Rot</font>
<font color=green>Grün</font>
<font color=grey>Grau</font>
<font color=blue>Blau</font>
<font color=orange>Orange</font>
<font color=wathet>Wasserblau</font>
<font color=purple>Lila</font>
Vollständige Farben: red / green / grey / blue / orange / wathet / purple / yellow / turquoise / indigo / violet / lime
Tags / Zahlen-Badges
<text_tag color='red'>Tag</text_tag>
<number_tag>99</number_tag>
Lokalisierte Zeit
<local_datetime millisecond='1700000000000' format_type='date_num'></local_datetime>
format_type: date_num (2023/11/15) / date_full (15. November 2023) / datetime (mit Stunden/Minuten/Sekunden)
Überschriften / Listen / Zitate
# H1 ## H2 ### H3 #### H4 ##### H5 ###### H6
- Ungeordnet 1. Geordnet > Zitat
Codeblöcke
```python
print("hallo")
```
Bilder (innerhalb von Markdown)

Tabellen (maximal 5 Datenzeilen)
| Spalte1 | Spalte2 |
|-----|-----|
| A | B |
Links
[Text](https://url) [Telefon](tel://13800138000)
Trennlinien / Zeilenumbrüche
--- oder <hr>
<br/> oder zweimal Eingabetaste (Absatzumbruch)
Emojis
⚠️ Strenge Regeln
- Es dürfen nur die Keys der offiziellen Liste verwendet werden. Nicht erfasste Keys (wie
:WAITING:,:PROCESSING:) werden als Klartext gerendert und sind verboten. - Die Groß-/Kleinschreibung der Keys muss exakt mit der offiziellen Liste übereinstimmen (z. B.
FirenichtFIRE,TrophynichtTROPHY,ShrugnichtSHRUG). - Innerhalb von
<text_tag>wird:KEY:nicht gerendert, stattdessen müssen Unicode-Emojis verwendet werden (z. B.✅). :KEY:funktioniert nur im normalen Textbereich vonlark_md.
Häufig verwendet für Release-Benachrichtigungen
:DONE: :OK: :THUMBSUP: :APPLAUSE:
:PARTY: :FIREWORKS: :MUSCLE: :THANKS:
:Trophy: :Fire: :Hundred: :CheckMark:
Arbeit & Status
:LGTM: :OnIt: :OneSecond: :Get:
:ERROR: :CrossMark: :Pin: :Alarm:
:Loudspeaker: :OKR: :Yes: :No:
:HEADSET: :Typing: :MeMeMe: :Sigh:
:Lemon: :AWESOMEN: :VRHeadset: :YouAreTheBest:
:SALUTE: :HIGHFIVE: :FISTBUMP: :FINGERHEART:
:WAVE: :ThumbsDown:
Gesichts-Emojis – Positiv
:SMILE: :LAUGH: :LOL: :LOVE:
:WINK: :PROUD: :WITTY: :JOYFUL:
:WOW: :BLUSH: :HUG: :BeamingFace:
:Delighted: :ThanksFace: :SaluteFace: :PRAISE:
:CLAP: :JIAYI: :FACEPALM: :YEAH:
Gesichts-Emojis – Negativ / Lustig
:CRY: :SOB: :ANGRY: :SHOCKED:
:Shrug: :THINKING: :DIZZY: :SLEEP:
:YAWN: :SICK: :SWEAT: :EMBARRASSED:
:SKULL: :RAINBOWPUKE: :PUKE: :ClownFace:
:ColdSweat: :SMUG: :SILENT: :TERROR:
:PETRIFIED: :BETRAYED: :WRONGED:
Feiern & Feiertage
:PARTY: :FIREWORKS: :REDPACKET: :FORTUNE:
:FIRECRACKER: :XmasTree: :XmasHat: :Snowman:
:Pumpkin: :Mooncake: :MoonRabbit: :Partying:
:StickyRiceBalls: :JubilantRabbit: :LUCK:
:FullMoonFace: :GoGoGo: :HappyDragon:
Gegenstände & Essen
:ROSE: :HEART: :BEER: :CAKE:
:GIFT: :BOMB: :Fire: :Trophy:
:Coffee: :BubbleTea: :Drumstick: :Pepper:
:CUCUMBER: :CANDIEDHAWS: :Soccer: :Basketball:
:Music: :CLEAVER: :EatingFood: :LIPS:
Statusanzeigen (nicht häufig verwenden, nur für Status-Szenarien)
:GeneralDoNotDisturb: :GeneralInMeetingBusy:
:StatusReading: :GeneralWorkFromHome:
:GeneralBusinessTrip: :StatusInFlight:
:GeneralSun: :GeneralMoonRest:
:StatusFlashOfInspiration: :Status_PrivateMessage:
:GeneralTravellingCar: :StatusBus:
Unicode-Emojis (in Titeln, text_tag, Header-Subtiteln)
🎉 ✅ ❌ ⚠️ 🚀 📦 💬 ⏳ 🔔 📝 🛠️ 🔍 👋 🎯 💡 🌟
👥 📅 🤖 🏆 🔥 💯 📣 🎁 ☕ 🍺
Empfohlener Stil: Titel mit Unicode-Emojis für visuelle Verstärkung, Status/Rückmeldungen mit Feishu-spezifischer Syntax wie
:DONE::LGTM:
Senden / Antworten / Aktualisieren
An Gruppe senden (neue Nachricht)
CARD=$(python3 -c "
import json
card = {
'schema': '2.0',
'header': {'title': {'tag': 'plain_text', 'content': 'Titel'}, 'template': 'blue'},
'body': {'elements': [{'tag': 'markdown', 'content': 'Inhalt'}]}
}
print(json.dumps(card))
")
lark-cli im +messages-send \
--chat-id oc_xxx \
--msg-type interactive \
--content "$CARD" \
--as bot
Auf bestimmte Nachricht antworten (message_id für spätere Aktualisierung abrufen)
MSG_ID=$(lark-cli im +messages-reply \
--message-id om_xxx \
--msg-type interactive \
--content "$CARD" \
--as bot \
--jq '.data.message_id')
In Thread antworten (erscheint nicht im Haupt-Chat-Feed)
lark-cli im +messages-reply \
--message-id om_xxx \
--msg-type interactive \
--content "$CARD" \
--reply-in-thread \
--as bot
Duplikate vermeiden (innerhalb von 1 Stunde wird derselbe Key nur einmal gesendet)
lark-cli im +messages-send \
--chat-id oc_xxx \
--msg-type interactive \
--content "$CARD" \
--idempotency-key "deploy-654.0-notify" \
--as bot
Karte lokal aktualisieren (PATCH)
PAYLOAD=$(jq -n --argjson card "$CARD" '{"msg_type":"interactive","content":($card|tojson)}')
lark-cli api PATCH "/open-apis/im/v1/messages/${MSG_ID}" \
--data "$PAYLOAD" \
--as bot
Kartenvorlagen
Wird gedacht (Streaming-Status, zuerst Platzhalter senden)
make_thinking_card() {
local name="$1" question="$2"
jq -n --arg name "$name" --arg q "$question"
...
[truncated for translation]