Build tabs with Adaptive Cards

Warning

Adaptive Card tabs aren't available in the new Teams client. The Classic Teams client is expected to be deprecated by March 31, 2024. If your app is using Adaptive Card tabs, we recommend to rebuild the tab as a web-based tab. For more information, see Build tabs for Teams.

When developing a tab using the traditional method, you might run into these issues:

  • HTML and CSS considerations
  • Slow load times
  • iFrame constraints
  • Server maintenance and costs

You can build Adaptive Card tabs in Teams. Instead of embedding web content in an iFrame, you can render Adaptive Cards to a tab. While the front end is rendered with Adaptive Cards, the backend is powered by a bot. The bot is responsible for accepting requests and responding appropriately with the Adaptive Card that is rendered.

You can build your tabs with ready-made user interface (UI) building blocks native on desktop, web, and mobile. This article helps you understand the changes required to be made to the app manifest. The article also identifies how the invoke activity requests and sends information in tab with Adaptive Cards, and its effect on the modal dialog (referred as task module in TeamsJS v1.x) workflow.

The following image shows build tabs with Adaptive Cards in desktop and mobile:

Screenshot shows an example of the Adaptive Card rendered in tabs.

Prerequisites

Before you start using Adaptive Cards to build tabs, you must:

Changes to app manifest

Personal apps that render tabs must include a staticTabs array in their app manifest. Adaptive Card tabs are rendered when the contentBotId property is provided in the staticTab definition. Static tab definitions must contain either a contentBotId, specifying an Adaptive Card tab or a contentUrl, specifying a typical hosted web content tab experience.

Note

The contentBotId property is available in manifest version 1.9 or later.

Provide the contentBotId property with the botId that the Adaptive Card tab must communicate with. The entityId configured for the Adaptive Card tab is sent in the tabContext parameter of each invoke request, and can be used to differentiate Adaptive Card Tabs that are powered by the same bot. For more information about other static tab definition fields, see manifest schema.

Following is a sample Adaptive Card tab manifest:

{
  "$schema": "https://raw.githubusercontent.com/OfficeDev/microsoft-teams-app-schema/preview/DevPreview/MicrosoftTeams.schema.json",
  "manifestVersion": "1.9",
  "id": "00000000-0000-0000-0000-000000000000",
  "version": "0.0.1",
  "developer": {
    "name": "Contoso",
    "websiteUrl": "https://contoso.yourwebsite.com",
    "privacyUrl": "https://contoso.yourwebsite.com/privacy.html",
    "termsOfUseUrl": "https://contoso.yourwebsite.com/terms.html"
  },
  "name": {
    "short": "Contoso",
    "full": "Contoso Home"
  },
  "description": {
    "short": "Add short description here",
    "full": "Add full description here"
  },
  "icons": {
    "outline": "icon-outline.png",
    "color": "icon-color.png"
  },
  "accentColor": "#D85028",
  "configurableTabs": [],
  "staticTabs": [
    {
      "entityId": "homeTab",
      "name": "Home",
      "contentBotId": "00000000-0000-0000-0000-000000000000",
      "scopes": ["personal"]
    },
    {
      "entityId": "moreTab",
      "name": "More",
      "contentBotId": "00000000-0000-0000-0000-000000000000",
      "scopes": ["personal"]
    }
  ],
  "connectors": [],
  "composeExtensions": [],
  "permissions": ["identity", "messageTeamMembers"],
  "validDomains": [
    "contoso.yourwebsite.com",
    "token.botframework.com"
  ]
}

Invoke activities

Communication between your Adaptive Card tab and your bot is done through invoke activities. Each invoke activity has a corresponding name. Use the name of each activity to differentiate each request. tab/fetch and tab/submit are the activities covered in this section.

Note

  • Bots need to send all the responses to service URL. Service URL is received as part of incoming activity payload.
  • The invoke payload size has increased to 80kb.

Fetch Adaptive Card to render to a tab

tab/fetch is the first invoke request that your bot receives when a user opens an Adaptive Card tab. When your bot receives the request, it either sends a tab continue response or a tab auth response. The continue response includes an array for cards, which is rendered vertically to the tab in the order of the array.

Note

For more information on auth response, see authentication.

The following code provides examples of tab/fetch request and response:

tab/fetch request

// tab/fetch POST request: agents/{botId}/invoke
{
    "name": "tab/fetch",
    "value: {
        "tabContext": {
            "tabEntityId": "{tab_entity_id}"
        },
        "context": {
            "theme": "default"
            }
    },
    "conversation": {
        "id": "{generated_conversation_id}"
    },
    "imdisplayname": "{user_display_name}"
}

tab/fetch response

// tab/fetch **continue** POST response:
{
    "tab": {
        "type": "continue",
        "value": {
            "cards": [
                {
                    "card": adaptiveCard1,
                },
                {
                    "card": adaptiveCard2,
                },
                {
                    "card": adaptiveCard3
                }  
            ]
        },
    },
    "responseType": "tab"
}

Handle submits from Adaptive Card

After an Adaptive Card is rendered in the tab, it can respond to user interactions. This response is handled by the tab/submit invoke request.

When a user selects a button on the Adaptive Card tab, the tab/submit request is triggered to your bot with the corresponding data through the Action.Submit function of Adaptive Card. The Adaptive Card data is available through the data property of the tab/submit request. You receive either of the following responses to your request:

  • An HTTP status code 200 response with no body. An empty 200 response results in no action taken by the client.
  • The standard 200 tab continue response, as explained in fetch Adaptive Card. A tab continue response triggers the client to update the rendered Adaptive Card tab with the Adaptive Cards provided in the cards array of the continue response.

The following code provides examples of tab/submit request and response:

tab/submit request

// tab/submit POST request: agents/{botId}/invoke:
{
    "name": "tab/submit",
    "value": {
        "data": {
            "type": "tab/submit",
            //...<data properties>
            },
        "context": {
            "theme": "default"
            },
        "tabContext": {
            "tabEntityId": "{tab_entity_id}"
            },
        },
    "conversation": {
           "id": "{generated_conversation_id}" 
        },
    "imdisplayname": "{user_display_name}"
}

tab/submit response

//tab/fetch **continue** POST response:
{
    "tab": {
        "type": "continue",
        "value": {
            "cards": [
              {
                "card": adaptiveCard1,
                },
              {
                "card": adaptiveCard2,
                } 
            ]
        },
    },
    "responseType": "tab"
}

Understand dialog workflow

Modal dialogs also use Adaptive Cards to invoke task/fetch and task/submit requests and responses. For more information, see using dialogs in Microsoft Teams bots.

With the introduction of Adaptive Card tab, there's a change in how the bot responds to a task/submit request. If you're using an Adaptive Card tab, the bot responds to the task/submit invoke request with the standard tab continue response, and closes the dialog. The Adaptive Card tab is updated by rendering the new list of cards provided in the tab continue response body.

Invoke task/fetch

The following code provides examples of task/fetch request and response:

task/fetch request

// task/fetch POST request: agents/{botId}/invoke
{
    "name": "task/fetch",
    "value": {
        "data": {
            "type": "task/fetch"
        },
        "context": {
            "theme": "default",
        },
        "tabContext": {
            "tabEntityId": "{tab_entity_id}"
        }
    },
    "imdisplayname": "{user_display_name}",
    "conversation": {
        "id": "{generated_conversation_id}"
    } 
}

task/fetch response

// task/fetch POST response: agents/{botId}/invoke
{
    "task": {
        "value": {
            "title": "Ninja Cat",
            "height": "small",
            "width": "small",
            "card": {
                "contentType": "application/vnd.microsoft.card.adaptive",
                "content": adaptiveCard,
            }
        },
    "type": "continue"
    },
    "responseType": "task"
}

Invoke task/submit

The following code provides examples of task/submit request and response:

task/submit request

// task/submit POST request: agent/{botId}/invoke:
{
    "name": "task/submit",
    "value": {
        "data": {serialized_data_object},
        "context": {
            "theme": "default"
        },
    "tabContext": {
        "tabEntityId": "{tab_entity_id}"
        },
    },
    "conversation": {
        "id": "{generated_conversation_id}"
    },
    "imdisplayname": "{user_display_name}",
}

task/submit tab response type

// tab/fetch **continue** POST response: 
{
    "task":{
        "value": {
            "tab": {
                "type": "continue",
                "value": {
                    "cards": [
                        {
                            "card": adaptiveCard1
                        },
                        {
                            "card": adaptiveCard2
                        }
                    ]
                }
            }
        },
        "type": "continue"
    },
    "responseType": "task"
}

Authentication

In the previous sections, you've seen that most of the development paradigms can be extended from the dialog requests and responses into tab requests and responses. When it comes to handling authentication, the workflow for Adaptive Card tab follows the authentication pattern for message extensions. For more information, see add authentication.

tab/fetch requests can have either a continue or an auth response. When a tab/fetch request is triggered and receives a tab auth response, the sign in page is shown to the user.

To get an authentication code through tab/fetch invoke

  1. Open your app. The sign in page appears.

    Note

    The app logo is provided through the icon property defined in the app manifest. The title appearing after the logo is defined in the title property returned in the tab auth response body.

  2. Select Sign in. You're redirected to the authentication URL provided in the value property of the auth response body.

  3. A pop-up window appears. This pop-up window hosts your web page using the authentication URL.

  4. After you sign in, close the window. An authentication code is sent to the Teams client.

  5. The Teams client then reissues the tab/fetch request to your service, which includes the authentication code provided by your hosted web page.

tab/fetch authentication data flow

The following image provides an overview of how the authentication data flow works for a tab/fetch invoke.

Screenshot shows the example of Adaptive Card Tab auth flow.

tab/fetch auth response

The following code provides an example of tab/fetch auth response:

// tab/auth POST response (openURL)
{
    "tab": {
        "type": "auth",
        "suggestedActions":{
            "actions":[
                {
                    "type": "openUrl",
                    "value": "https://example.com/auth",
                    "title": "Sign in to this app"
                }
            ]
        }
    }
}

Example

The following code shows a reissued request example:

{
    "name": "tab/fetch",
    "type": "invoke",
    "timestamp": "2021-01-15T00:10:12.253Z",
    "channelId": "msteams",
    "serviceUrl": "https://smba.trafficmanager.net/amer/",
    "from": {
        "id": "{id}",
        "name": "John Smith",
        "aadObjectId": "00000000-0000-0000-0000-000000000000"
    },
    "conversation": {
        "tenantId": "{tenantId}",
        "id": "tab:{guid}"
    },
    "recipients": {
        "id": "28:00000000-0000-0000-0000-000000000000",
        "name": "ContosoApp"
    },
    "entities": [
        {
            "locale": "en-us",
            "country": "US",
            "platform": "Windows",
            "timezone": "America/Los_Angeles",
            "type": "clientInfo"
        }
    ],
    "channelData": {
        "tenant": { "id": "00000000-0000-0000-0000-000000000000" },
        "source": { "name": "message" }
    },
    "value": {
        "tabContext": { "tabEntityId": "homeTab" },
        "state": "0.43195668034524815"
    },
    "locale": "en-US",
    "localTimeZone": "America/Los_Angeles"
}

Code sample

Sample name Description .NET Node.js Manifest
Show Adaptive Cards in Teams tab Microsoft Teams tab sample code, which demonstrates how to show Adaptive Cards in Teams. View View View

Step-by-step guide

Follow the step-by-step guide to build tab with Adaptive Cards.

Next step

See also