Microsoft Teams Error Guide: 'This card couldn't be displayed' — Adaptive Card Render Failures
Fix 'This card couldn't be displayed' in Microsoft Teams: pin the schema version Teams supports, add element fallback, wrap payloads correctly, and stay under the card size limit.
- #microsoft-teams
- #adaptive-cards
- #troubleshooting
- #errors
Stuck on this Microsoft Teams error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
An Adaptive Card that posts successfully but renders as a grey placeholder is one of the most confusing Teams failures: the HTTP call returned 200/202, yet the recipient sees no card content. Teams renders this fallback text in the message body:
This card couldn't be displayed. [Please update Teams / open in browser]
In bot scenarios the underlying activity may also surface a Bot Framework error before it ever reaches the client:
{
"error": {
"code": "BadRequest",
"message": "Invalid Card: the card version '1.6' is not supported by this host."
}
}
The root problem is almost always a mismatch between the Adaptive Card you sent and what the specific Teams host can render — a schema version set too high, an element the host does not support with no fallback, an invalid card body, or a payload that is not wrapped the way the channel expects.
Symptoms
- The message posts (HTTP 200/201/202) but the card area shows “This card couldn’t be displayed.”
- The card renders on Teams desktop but is blank on mobile, or vice versa.
- The card renders in the Adaptive Cards Designer but fails inside Teams.
- A bot log shows
Invalid Card/BadRequestwhen callingPOST /v3/conversations/{id}/activities. - An incoming Workflows webhook returns HTTP 400 or posts an empty card.
- The whole card blanks even though only one new element (a
Table,Input, orAction.Execute) is unsupported.
Common Root Causes
1. Schema version set higher than the host supports
Teams does not render the newest Adaptive Cards schema. Each host caps at a specific version, and setting "version" above that makes the entire card fail closed — nothing renders.
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6",
"body": [
{ "type": "TextBlock", "text": "Deploy succeeded" }
]
}
Modern Teams clients support up to Adaptive Cards 1.5. A card declaring 1.6 is rejected wholesale. Pin version to 1.5 (or lower for Outlook Actionable Messages).
2. An unsupported element with no fallback
If a card uses an element or action that requires a newer version than the host supports and provides no fallback, the host drops the entire card, not just that element.
{
"type": "Table",
"columns": [ { "width": 1 }, { "width": 1 } ],
"rows": []
}
Table requires 1.5. On a 1.4 host with no fallback, the card blanks. Add element-level fallback so only that element degrades:
{
"type": "Table",
"columns": [ { "width": 1 }, { "width": 1 } ],
"rows": [],
"fallback": {
"type": "TextBlock",
"text": "Table view requires a newer Teams client.",
"wrap": true
}
}
3. Payload not wrapped in the message envelope (webhooks)
The Power Automate Workflows inbound webhook and the Bot Framework both expect the card inside an attachment envelope, not a bare Adaptive Card. Posting the raw card returns 400 or a blank message.
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": { "type": "AdaptiveCard", "version": "1.5", "body": [] }
}
]
}
4. Invalid card JSON or wrong contentType
A trailing comma, a missing type on an element, or contentType: "application/vnd.microsoft.card.hero" on Adaptive Card content all produce a render failure. The card must be valid JSON and the contentType must be application/vnd.microsoft.card.adaptive.
5. Card exceeds the size limit
Teams caps a single Adaptive Card payload at roughly 28 KB. Cards that inline large log dumps, big images as base64, or hundreds of rows exceed the limit and fail to render. Truncate content and link out to a dashboard instead of inlining.
6. Templating that produced empty or malformed output
When using Adaptive Card Templating (${...} bindings), an unbound expression or a null data field can emit undefined, a broken structure, or an empty body, all of which render as the fallback placeholder.
Diagnostic Workflow
Step 1: Validate the JSON and the version
echo "$CARD" | jq empty && echo "JSON valid" || echo "JSON INVALID"
echo "$CARD" | jq -r '.version // .attachments[0].content.version'
If version is above 1.5, that alone explains a total render failure on Teams.
Step 2: Confirm the wrapper for webhook posts
Test the Workflows inbound webhook with a correctly wrapped, minimal card and check the HTTP status:
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$WORKFLOW_URL" \
-H "Content-Type: application/json" \
-d '{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [ { "type": "TextBlock", "text": "render test", "wrap": true } ]
}
}
]
}'
202 means the payload shape is accepted. If a bare card returned 400 but the wrapped one returns 202, the missing envelope was the cause.
Step 3: Check the payload size
echo "$CARD" | wc -c
Anything approaching 28000 bytes is at risk. Trim inlined content.
Step 4: Bisect elements to find the unsupported one
Post the card with only a TextBlock, confirm it renders, then add elements back one at a time. The element that blanks the card is the one that needs fallback or a lower version.
# Minimal known-good card
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$WORKFLOW_URL" \
-H "Content-Type: application/json" \
-d '{"type":"message","attachments":[{"contentType":"application/vnd.microsoft.card.adaptive","content":{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"ok"}]}}]}'
Step 5: Reproduce in the Designer with the Teams host config
Open https://adaptivecards.io/designer, select the Microsoft Teams host config, and paste the card. The Designer flags unsupported elements and version mismatches inline, matching what Teams enforces.
Example Root Cause Analysis
A deployment pipeline posts a summary card containing a Table of the last ten releases. The card renders fine on the engineer’s desktop client during testing but shows “This card couldn’t be displayed” for half the channel — specifically teammates on older mobile clients.
Inspecting the card:
echo "$CARD" | jq '{version, hasTable: (.body[] | select(.type=="Table")) != null}'
{
"version": "1.5",
"hasTable": true
}
The card correctly declares 1.5, but the affected mobile clients had not yet updated to a build that renders Table, and there was no fallback, so the entire card blanked for them. The fix adds an element-level fallback so older clients drop only the table and still see the summary and buttons:
{
"type": "Table",
"columns": [ { "width": 1 }, { "width": 2 }, { "width": 1 } ],
"rows": [],
"fallback": {
"type": "TextBlock",
"text": "Open the dashboard to view the release table.",
"wrap": true
}
}
A top-level fallbackText was also added so notification previews and the oldest clients show a meaningful line instead of a blank placeholder. After redeploying, every client rendered at least the summary; modern clients rendered the full table.
Prevention Best Practices
- Pin
versionto the highest version Teams actually supports (currently1.5), never to the latest published schema — a too-high version fails the whole card closed. - Add element-level
fallback(or"fallback": "drop") to every element or action that requires a newer version than your floor, so one unsupported element never blanks the card. - Always include a top-level
fallbackTextso hosts that cannot render the card at all still show useful text. - Validate card JSON with
jq emptyin CI and render-test in the Adaptive Cards Designer with the Teams host config before shipping. - Keep cards well under the ~28 KB limit — truncate logs, avoid base64 images, and link out to dashboards instead of inlining large content.
- For webhook and bot posts, always wrap the card in the
{"type":"message","attachments":[...]}envelope withcontentType: application/vnd.microsoft.card.adaptive. - Cross-reference render failures against the Microsoft Teams guides and the incident assistant to match symptoms to root cause quickly.
Quick Command Reference
# Validate card JSON
echo "$CARD" | jq empty && echo valid || echo INVALID
# Show declared schema version
echo "$CARD" | jq -r '.version // .attachments[0].content.version'
# Check payload size (limit ~28 KB)
echo "$CARD" | wc -c
# Post a minimal wrapped card to a Workflows webhook and print status
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$WORKFLOW_URL" \
-H "Content-Type: application/json" \
-d '{"type":"message","attachments":[{"contentType":"application/vnd.microsoft.card.adaptive","content":{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"ok"}]}}]}'
# Extract all element types to spot version-gated elements (Table, Input.*, ActionSet)
echo "$CARD" | jq '[.. | .type? // empty] | unique'
Conclusion
“This card couldn’t be displayed” is a rendering contract failure, not an auth or permission problem. In order of frequency the causes are:
versionset higher than the Teams host supports, failing the whole card closed.- An unsupported element (
Table, newerInput,Action.Execute) with nofallback, blanking the entire card. - The card posted without the
{"type":"message","attachments":[...]}envelope on a webhook or bot. - Invalid card JSON or the wrong
contentType. - A payload exceeding the ~28 KB card size limit.
- Templating that emitted empty or malformed output.
The fastest fix path is: validate the JSON, pin version to 1.5, add fallback plus fallbackText, confirm the message envelope, and render-test in the Adaptive Cards Designer with the Teams host config before shipping.
Fixed it? Get 500 Microsoft Teams & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.