diff --git a/msteams-platform/messaging-extensions/how-to/link-unfurling.md b/msteams-platform/messaging-extensions/how-to/link-unfurling.md index 325c8cc53de..4f32925a2f6 100644 --- a/msteams-platform/messaging-extensions/how-to/link-unfurling.md +++ b/msteams-platform/messaging-extensions/how-to/link-unfurling.md @@ -1,10 +1,12 @@ --- title: Add Link Unfurling to App Manifest +author: vikasalmal description: Learn to add link unfurling with Developer Portal and messaging extension in a Teams app with app manifest or manually. Update web service code to handle invoke. ms.localizationpriority: medium ms.topic: conceptual +ms.author: vikasalmal ms.owner: slamba -ms.date: 11/06/2024 +ms.date: 04/23/2026 --- # Link unfurling @@ -121,7 +123,7 @@ The following card types are supported: * [Hero card](~/task-modules-and-cards/cards/cards-reference.md#hero-card) * [Connector card for Microsoft 365 Groups](../../task-modules-and-cards/cards/cards-reference.md#connector-card-for-microsoft-365-groups) -For more information, see [Action type invoke](~/task-modules-and-cards/cards/cards-actions.md#action-type-invoke). +For more information, see [Action.Execute](~/task-modules-and-cards/cards/cards-actions.md#actionexecute). The following code is an example of the `invoke` request: @@ -364,7 +366,7 @@ To get your app ready for zero install link unfurling, follow these steps: } }, "value":{ - "url":"https://test.test.com/test" + "url":"https://example.com/test" }, "locale":"en-US", "localTimezone":"America/Los_Angeles" @@ -535,4 +537,4 @@ The following JSON payload example for `suggestedActions` property: * [Message extensions](../what-are-messaging-extensions.md) * [Adaptive Cards](../../task-modules-and-cards/what-are-cards.md#adaptive-cards) * [Tabs link unfurling and Stageview](../../tabs/tabs-link-unfurling.md) -* [Bot activity handlers](../../bots/bot-basics.md) \ No newline at end of file +* [Bot activity handlers](../../bots/bot-basics.md) diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Sequential-Workflows.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Sequential-Workflows.md index 6de620eb99d..700d6f7905b 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Sequential-Workflows.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Sequential-Workflows.md @@ -145,7 +145,7 @@ adaptive_card_response = { ## See also * [Cards and dialogs](../../cards-and-task-modules.md) -* [Adaptive Card actions in Teams](~/task-modules-and-cards/cards/cards-actions.md#adaptive-cards-actions) +* [Adaptive Card actions in Teams](~/task-modules-and-cards/cards/cards-actions.md#card-actions) * [How bots work](/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0&preserve-view=true) * [Work with Universal Actions for Adaptive Cards](Work-with-universal-actions-for-adaptive-cards.md) -* [Form completion feedback](../cards-actions.md#form-completion-feedback) +* [Server handlers](../cards-actions.md#server-handlers) diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Up-To-Date-Views.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Up-To-Date-Views.md index aac0c82030d..ee06d298cf7 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Up-To-Date-Views.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Up-To-Date-Views.md @@ -265,4 +265,4 @@ The following code provides an example of Adaptive Cards sent as response of `ad * [Cards and dialogs](../../cards-and-task-modules.md) * [Work with Universal Actions for Adaptive Cards](Work-with-universal-actions-for-adaptive-cards.md) * [User Specific Views](User-Specific-Views.md) -* [Form completion feedback](../cards-actions.md#form-completion-feedback) +* [Server handlers](../cards-actions.md#server-handlers) diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/User-Specific-Views.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/User-Specific-Views.md index 35573d2a276..b28447c02c1 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/User-Specific-Views.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/User-Specific-Views.md @@ -270,4 +270,4 @@ The following list provides card design guidelines for User Specific Views: * [Work with Universal Actions for Adaptive Cards](Work-with-Universal-Actions-for-Adaptive-Cards.md) * [Up to date cards](Up-To-Date-Views.md) * [Cards](../../what-are-cards.md) -* [Form completion feedback](../cards-actions.md#form-completion-feedback) +* [Server handlers](../cards-actions.md#server-handlers) diff --git a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Work-with-Universal-Actions-for-Adaptive-Cards.md b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Work-with-Universal-Actions-for-Adaptive-Cards.md index ea4ca749e23..2aa170970a4 100644 --- a/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Work-with-Universal-Actions-for-Adaptive-Cards.md +++ b/msteams-platform/task-modules-and-cards/cards/Universal-actions-for-adaptive-cards/Work-with-Universal-Actions-for-Adaptive-Cards.md @@ -117,7 +117,7 @@ For more information, see [backward compatibility on Teams](/adaptive-cards/auth ## See also * [Cards and dialogs](../../cards-and-task-modules.md) -* [Adaptive Card actions in Teams](~/task-modules-and-cards/cards/cards-actions.md#adaptive-cards-actions) +* [Adaptive Card actions in Teams](~/task-modules-and-cards/cards/cards-actions.md#card-actions) * [Sequential Workflows](~/task-modules-and-cards/cards/universal-actions-for-adaptive-cards/sequential-workflows.md) * [Up to date cards](~/task-modules-and-cards/cards/universal-actions-for-adaptive-cards/up-to-date-views.md) -* [Form completion feedback](../cards-actions.md#form-completion-feedback) +* [Server handlers](../cards-actions.md#server-handlers) diff --git a/msteams-platform/task-modules-and-cards/cards/cards-actions.md b/msteams-platform/task-modules-and-cards/cards/cards-actions.md index 91d82bd4974..710f829b0a9 100644 --- a/msteams-platform/task-modules-and-cards/cards/cards-actions.md +++ b/msteams-platform/task-modules-and-cards/cards/cards-actions.md @@ -1,546 +1,679 @@ ---- -title: Add card actions in a bot -description: Learn about card actions such as openUrl, messageBack, imBack, invoke, and signin, and Adaptive Card actions such as Action.Submit. +--- +title: Adaptive Card actions in Teams SDK +description: Learn about Adaptive Card action types such as Action.Execute, Action.OpenUrl, Action.ShowCard, and Action.ToggleVisibility, and how to handle card actions using the Teams SDK. ms.localizationpriority: medium ms.topic: conceptual -ms.date: 11/07/2024 +ms.date: 04/28/2026 --- + + # Card actions [!INCLUDE [adaptive-card-redirect](../../includes/adaptive-card-redirect.md)] -Cards used by bots and message extensions in Microsoft Teams support the following activity [`CardAction`](/bot-framework/dotnet/bot-builder-dotnet-add-rich-card-attachments#process-events-within-rich-cards) types: +Adaptive Cards support interactive elements through actions buttons, links, and input submission triggers that respond to user interaction. You can use these to collect form input, trigger workflows, open URLs, and more. -> [!NOTE] -> The `CardAction` actions differ from `potentialActions` for connector cards for Microsoft 365 Groups when used from connectors. +The Teams SDK provides builder helpers and server-side handlers that simplify working with card actions. The following action types are supported: -| Type | Action | -| --- | --- | -| `openUrl` | Opens a URL in the default browser. | -| `messageBack` | Sends a message and payload to the bot from the user who selected the button or tapped the card. Sends a separate message to the chat stream. | -| `imBack`| Sends a message to the bot from the user who selected the button or tapped the card. This message from user to bot is visible to all conversation participants. | -| `invoke` | Sends a message and payload to the bot from the user who selected the button or tapped the card. This message isn't visible. | -| `signin` | Initiates OAuth flow, allowing bots to connect with secure services. | +| Action type | Use case | Description | +| --- | --- | --- | +| `Action.Execute` | Server-side processing | Sends data to your bot for processing. Best for forms and multi-step workflows. | +| `Action.Submit` | Simple data submission | Legacy action type. Prefer `Action.Execute` for new projects. | +| `Action.OpenUrl` | External navigation | Opens a URL in the user's browser. | +| `Action.ShowCard` | Progressive disclosure | Displays a nested card when selected. | +| `Action.ToggleVisibility` | UI state management | Shows or hides card elements dynamically. | > [!NOTE] -> ->* Teams does not support `CardAction` types not listed in the previous table. ->* Teams does not support the `potentialActions` property. ->* Card actions are different than [suggested actions](/azure/bot-service/bot-builder-howto-add-suggested-actions?view=azure-bot-service-4.0&tabs=javascript&preserve-view=true#suggest-action-using-button) in Bot Framework or Azure Bot Service. ->* If you are using a card action as part of a message extension, the actions do not work until the card is submitted to the channel. The actions do not work while the card is in the compose message box. +> For complete reference on action types, see the [Adaptive Cards documentation](https://adaptivecards.microsoft.com/?topic=Action.Execute). -## Action type openUrl +## Create actions with the SDK -`openUrl` action type specifies a URL to launch in the default browser. - -> [!NOTE] -> -> * Your bot doesn't receive any notice on which button was selected. -> * URLs don't support machine names that include numbers. For example, a hostname such as *userhostname123* isn't supported. -> * When using `Action.OpenUrl`, make sure to include the domain of the target URL in the validDomains section of your app manifest. If the domain isn’t listed, Teams displays the message **URL may lead to untrusted content**. +The SDK provides builder helpers that abstract the underlying JSON. You can create actions using strongly typed classes from the `Microsoft.Teams.Cards` namespace. -With `openUrl`, you can create an action with the following properties: +### Action.Execute -| Property | Description | -| --- | --- | -| `title` | Appears as the button label. | -| `value` | This field must contain a full and properly formed URL. | - -# [JSON](#tab/json) - -The following code shows an example of `openUrl` action type in JSON: - -```json -{ - "type": "openUrl", - "title": "Tabs in Teams", - "value": "https://msdn.microsoft.com/microsoft-teams/tabs" -} -``` +`Action.Execute` is the recommended action type for server-side processing. When a user selects an Execute action, the input values and any configured data are sent to your bot as a `card.action` activity. -# [C#](#tab/csharp) +# [C#](#tab/csharp1) -The following code shows an example of `openUrl` action type in C#: +The following code shows an example of an `Action.Execute` action in C#: ```csharp -var button = new CardAction() +using Microsoft.Teams.Cards; + +var action = new ExecuteAction { - Type = ActionTypes.OpenUrl, - Title = "Tabs in Teams", - Value = "https://learn.microsoft.com/microsoftteams/platform/" + Title = "Submit Feedback", + Data = new Union(new SubmitActionData + { + NonSchemaProperties = new Dictionary + { + { "action", "submit_feedback" } + } + }), + AssociatedInputs = AssociatedInputs.Auto }; ``` -# [JavaScript/Node.js](#tab/javascript) +# [TypeScript](#tab/typescript1) -The following code shows an example of `openUrl` action type in JavaScript: +The following code shows an example of an `Action.Execute` action in TypeScript: -```javascript -CardFactory.actions([ -{ - type: 'openUrl', - title: 'Tabs in Teams', - value: 'https://learn.microsoft.com/microsoftteams/platform/' -}]) -``` +```typescript +import { ExecuteAction } from '@microsoft/teams.cards'; ---- +const action = new ExecuteAction({ title: 'Submit Feedback' }) + .withData({ action: 'submit_feedback' }) + .withAssociatedInputs('auto'); +``` -## Action type messageBack +# [Python](#tab/python1) -With `messageBack`, you can create a fully customized action with the following properties: +The following code shows an example of an `Action.Execute` action in Python: -| Property | Description | -| --- | --- | -| `title` | Appears as the button label. | -| `displayText` | Optional. Used by the user in the chat stream when the action is performed. This text isn't sent to your bot. | -| `value` | Sent to your bot when the action is performed. You can encode context for the action, such as unique identifiers or a JSON object. | -| `text` | Sent to your bot when the action is performed. Use this property to simplify bot development. Your code can check a single top-level property to dispatch bot logic. | +```python +from microsoft_teams.cards.core import ExecuteAction -The flexibility of `messageBack` means that your code can't leave a visible user message in the history simply by not using `displayText`. +action = ExecuteAction(title="Submit Feedback") \ + .with_data({"action": "submit_feedback"}) \ + .with_associated_inputs("auto") +``` -# [JSON](#tab/json) +# [JSON](#tab/json1) -The following code shows an example of `messageBack` action type in JSON: +The following code shows an example of an `Action.Execute` action in JSON: ```json { - "buttons": [ - { - "type": "messageBack", - "title": "My MessageBack button", - "displayText": "I clicked this button", - "text": "User just clicked the MessageBack button", - "value": "{\"property\": \"propertyValue\" }" - } - ] + "type": "Action.Execute", + "title": "Submit Feedback", + "data": { + "action": "submit_feedback" + }, + "associatedInputs": "auto" } ``` -The `value` property can be either a serialized JSON string or a JSON object. +--- + +### Action.OpenUrl -# [C#](#tab/csharp) +`Action.OpenUrl` opens a specified URL in the user's browser. -The following code shows an example of `messageBack` action type in C#: +> [!NOTE] +> When using `Action.OpenUrl`, make sure to include the domain of the target URL in the `validDomains` section of your app manifest. If the domain isn't listed, Teams displays the message **URL may lead to untrusted content**. -```csharp -var button = new CardAction() -{ - Type = ActionTypes.MessageBack, - Title = "My MessageBack button", - DisplayText = "I clicked this button", - Text = "User just clicked the MessageBack button", - Value = "{\"property\": \"propertyValue\" }" -}; -``` +# [C#](#tab/csharp2) -# [JavaScript/Node.js](#tab/javascript) +The following code shows an example of an `Action.OpenUrl` action in C#: -The following code shows an example of `messageBack` action type in JavaScript: +```csharp +using Microsoft.Teams.Cards; -```javascript -CardFactory.actions([ +var action = new OpenUrlAction("https://adaptivecards.microsoft.com") { - type: 'messageBack', - title: "My MessageBack button", - displayText: "I clicked this button", - text: "User just clicked the MessageBack button", - value: {property: "propertyValue" } -}]) + Title = "Learn More" +}; ``` ---- +# [TypeScript](#tab/typescript2) -### Inbound message example +The following code shows an example of an `Action.OpenUrl` action in TypeScript: -`replyToId` contains the ID of the message that the card action came from. Use it if you want to update the message. +```typescript +import { OpenUrlAction } from '@microsoft/teams.cards'; -The following code shows an example of inbound message: - -```json -{ - "text":"User just clicked the MessageBack button", - "value":{ - "property":"propertyValue" - }, - "type":"message", - "timestamp":"2017-06-22T22:38:47.407Z", - "id":"f:5261769396935243054", - "channelId":"msteams", - "serviceUrl":"https://smba.trafficmanager.net/amer-client-ss.msg/", - "from":{ - "id":"29:102jd210jd010icsoaeclaejcoa9ue09u", - "name":"John Smith" - }, - "conversation":{ - "id":"19:malejcou081i20ojmlcau0@thread.skype;messageid=1498171086622" - }, - "recipient":{ - "id":"28:76096e45-119f-4736-859c-6dfff54395f7", - "name":"MyBot" - }, - "entities":[ - { - "locale": "en-US", - "country": "US", - "platform": "Windows", - "timezone": "America/Los_Angeles", - "type": "clientInfo" - } - ], - "channelData":{ - "channel":{ - "id":"19:malejcou081i20ojmlcau0@thread.skype" - }, - "team":{ - "id":"19:12d021jdoijsaeoaue0u@thread.skype" - }, - "tenant":{ - "id":"bec8e231-67ad-484e-87f4-3e5438390a77" - } - }, - "replyToId": "1575667808184", -} +const action = new OpenUrlAction('https://adaptivecards.microsoft.com') + .withTitle('Learn More'); ``` -## Action type imBack - -The `imBack` action triggers a return message to your bot, as if the user typed it in a normal chat message. Your user and all other users in a channel can see the button response. +# [Python](#tab/python2) -With `imBack`, you can create an action with the following properties: +The following code shows an example of an `Action.OpenUrl` action in Python: -| Property | Description | -| --- | --- | -| `title` | Appears as the button label. | -| `value` | This field must contain the text string used in the chat and therefore sent back to the bot. This is the message text you process in your bot to perform the desired logic. | +```python +from microsoft_teams.cards.core import OpenUrlAction -> [!NOTE] -> The `value` field is a simple string. There is no support for formatting or hidden characters. +action = OpenUrlAction(url="https://adaptivecards.microsoft.com") \ + .with_title("Learn More") +``` -# [JSON](#tab/json) +# [JSON](#tab/json2) -The following code shows an example of `imBack` action type in JSON: +The following code shows an example of an `Action.OpenUrl` action in JSON: ```json { - "type": "imBack", - "title": "More", - "value": "Show me more" + "type": "Action.OpenUrl", + "url": "https://adaptivecards.microsoft.com", + "title": "Learn More" } ``` -# [C#](#tab/csharp) +--- + +### Action sets + +You can group multiple actions together using `ActionSet` within an Adaptive Card: -The following code shows an example of `imBack` action type in C#: +# [C#](#tab/csharp3) + +The following code shows an example of grouping actions in C#: ```csharp -var button = new CardAction() +using Microsoft.Teams.Cards; + +var card = new AdaptiveCard { - Type = ActionTypes.ImBack, - Title = "More", - Value = "Show me more" + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Actions = new List + { + new ExecuteAction + { + Title = "Submit Feedback", + Data = new Union(new SubmitActionData + { + NonSchemaProperties = new Dictionary + { + { "action", "submit_feedback" } + } + }) + }, + new OpenUrlAction("https://adaptivecards.microsoft.com") + { + Title = "Learn More" + } + } }; ``` -# [JavaScript/Node.js](#tab/javascript) +# [TypeScript](#tab/typescript3) -The following code shows an example of `imBack` action type in JavaScript: +The following code shows an example of grouping actions in TypeScript: -```javascript -CardFactory.actions([ -{ - type: "imBack", - title: "More", - value: "Show me more" -}]) -``` +```typescript +import { ActionSet, ExecuteAction, OpenUrlAction } from '@microsoft/teams.cards'; ---- - -## Action type invoke +const actionSet = new ActionSet( + new ExecuteAction({ title: 'Submit Feedback' }) + .withData({ action: 'submit_feedback' }) + .withAssociatedInputs('auto'), + new OpenUrlAction('https://adaptivecards.microsoft.com') + .withTitle('Learn More') +); +``` -The `invoke` action is used for invoking [dialogs (referred as task modules in TeamsJS v1.x)](~/task-modules-and-cards/task-modules/task-modules-bots.md). +# [Python](#tab/python3) -The `invoke` action contains three properties, `type`, `title`, and `value`. +The following code shows an example of grouping actions in Python: -With `invoke`, you can create an action with the following properties: +```python +from microsoft_teams.cards.core import ActionSet, ExecuteAction, OpenUrlAction -| Property | Description | -| --- | --- | -| `title` | Appears as the button label. | -| `value` | This property can contain a string, a stringified JSON object, or a JSON object. | +action_set = ActionSet( + actions=[ + ExecuteAction(title="Submit Feedback") + .with_data({"action": "submit_feedback"}), + OpenUrlAction(url="https://adaptivecards.microsoft.com") + .with_title("Learn More") + ] +) +``` -# [JSON](#tab/json) +# [JSON](#tab/json3) -The following code shows an example of `invoke` action type in JSON: +The following code shows an example of grouping actions in JSON: ```json { - "type": "invoke", - "title": "Option 1", - "value": { - "option": "opt1" + "type": "AdaptiveCard", + "version": "1.5", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "actions": [ + { + "type": "Action.Execute", + "title": "Submit Feedback", + "data": { + "action": "submit_feedback" + } + }, + { + "type": "Action.OpenUrl", + "url": "https://adaptivecards.microsoft.com", + "title": "Learn More" } + ] } ``` -When a user selects the button, your bot receives the `value` object with some additional information. +--- -> [!NOTE] -> The activity type is `invoke` instead of `message` that is `activity.Type == "invoke"`. +### Raw JSON alternative -# [C#](#tab/csharp) +If you prefer to work with raw JSON, you can deserialize it into the SDK types: -The following code shows an example of `invoke` action type in C#: +# [C#](#tab/csharp4) ```csharp -var button = new CardAction() +var actionJson = """ { - Title = "Option 1", - Type = "invoke", - Value = "{\"option\": \"opt1\"}" -}; + "type": "Action.OpenUrl", + "url": "https://adaptivecards.microsoft.com", + "title": "Learn More" +} +"""; +var action = OpenUrlAction.Deserialize(actionJson); ``` -# [JavaScript/Node.js](#tab/javascript) +# [TypeScript](#tab/typescript4) -The following code shows an example of `invoke` action type in Node.js: +```typescript +import { IOpenUrlAction } from '@microsoft/teams.cards'; -```javascript -CardFactory.actions([ -{ - type: "invoke", - title: "Option 1", - value: { - option: "opt1" - } -}]) +const actionJson = { + type: 'Action.OpenUrl', + url: 'https://adaptivecards.microsoft.com', + title: 'Learn More', +} as const satisfies IOpenUrlAction; +``` + +# [Python](#tab/python4) + +```python +action_json = { + "type": "Action.OpenUrl", + "url": "https://adaptivecards.microsoft.com", + "title": "Learn More", +} ``` --- -### Example of incoming invoke message +## Work with input values -The top-level `replyToId` property contains the ID of the message that the card action came from. Use it if you want to update the message. +### Associate data with cards -The following code shows an example of incoming invoke message: +You can send a card and have it be associated with specific data. Set the `data` value to be sent back to the client so you can associate it with a particular entity. -```json +# [C#](#tab/csharp5) + +The following code shows an example of associating data with card actions in C#: + +```csharp +using Microsoft.Teams.Cards; + +private static AdaptiveCard CreateProfileCard() { - "type": "invoke", - "value": { - "option": "opt1" - }, - "timestamp": "2017-02-10T04:11:19.614Z", - "localTimestamp": "2017-02-09T21:11:19.614-07:00", - "id": "f:6894910862892785420", - "channelId": "msteams", - "serviceUrl": "https://smba.trafficmanager.net/amer-client-ss.msg/", - "from": { - "id": "29:1Eniglq0-uVL83xNB9GU6w_G5a4SZF0gcJLprZzhtEbel21G_5h- - NgoprRw45mP0AXUIZVeqrsIHSYV4ntgfVJQ", - "name": "John Doe" - }, - "conversation": { - "id": "19:97b1ec61-45bf-453c-9059-6e8984e0cef4_8d88f59b-ae61-4300-bec0-caace7d28446@unq.gbl.spaces" - }, - "recipient": { - "id": "28:8d88f59b-ae61-4300-bec0-caace7d28446", - "name": "MyBot" - }, - "entities": [ + return new AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List { - "locale": "en-US", - "country": "US", - "platform": "Web", - "type": "clientInfo" - } - ], - "channelData": { - "channel": { - "id": "19:dc5ba12695be4eb7bf457cad6b4709eb@thread.skype" - }, - "team": { - "id": "19:712c61d0ef384e5fa681ba90ca943398@thread.skype" + new TextBlock("User Profile") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large + }, + new TextInput + { + Id = "name", + Label = "Name", + Value = "John Doe" + }, + new TextInput + { + Id = "email", + Label = "Email", + Value = "john@contoso.com" + }, + new ToggleInput("Subscribe to newsletter") + { + Id = "subscribe", + Value = "false" + } }, - "tenant": { - "id": "72f988bf-86f1-41af-91ab-2d7cd011db47" + Actions = new List + { + new ExecuteAction + { + Title = "Save", + Data = new Union(new SubmitActionData + { + NonSchemaProperties = new Dictionary + { + { "action", "save_profile" }, + { "entity_id", "12345" } + } + }), + AssociatedInputs = AssociatedInputs.Auto + } } - }, - "replyToId": "1575667808184" + }; } ``` -## Action type sign-in - -`signin` action type initiates an OAuth flow that permits bots to connect with secure services. For more information, see [authentication flow in bots](~/bots/how-to/authentication/auth-flow-bot.md). +When the user submits the card, the handler receives the input values merged with the action data: -Teams also supports [Adaptive Cards actions](#adaptive-cards-actions) that are only used by Adaptive Cards. +```text +data["action"] → "save_profile" +data["entity_id"] → "12345" +data["name"] → "John Doe" +data["email"] → "john@contoso.com" +data["subscribe"] → "true" +``` -# [JSON](#tab/json) +# [TypeScript](#tab/typescript5) + +The following code shows an example of associating data with card actions in TypeScript: + +```typescript +import { + AdaptiveCard, + TextInput, + ToggleInput, + ActionSet, + ExecuteAction, +} from '@microsoft/teams.cards'; + +function editProfileCard() { + const card = new AdaptiveCard( + new TextInput({ id: 'name' }).withLabel('Name').withValue('John Doe'), + new TextInput({ id: 'email', label: 'Email', value: 'john@contoso.com' }), + new ToggleInput('Subscribe to newsletter').withId('subscribe').withValue('false'), + new ActionSet( + new ExecuteAction({ title: 'Save' }) + .withData({ + action: 'save_profile', + entity_id: '12345', + }) + .withAssociatedInputs('auto') + ) + ); + + return card; +} +``` -The following code shows an example of `signin` action type in JSON: +When the user submits the card, the handler receives the input values merged with the action data: -```json -{ -"type": "signin", -"title": "Click me for signin", -"value": "https://signin.com" -} +```text +data.action → "save_profile" +data.entity_id → "12345" +data.name → "John Doe" +data.email → "john@contoso.com" +data.subscribe → "true" ``` -# [C#](#tab/csharp) +# [Python](#tab/python5) + +The following code shows an example of associating data with card actions in Python: + +```python +from microsoft_teams.cards import AdaptiveCard, ActionSet, ExecuteAction +from microsoft_teams.cards.core import TextInput, ToggleInput + +profile_card = AdaptiveCard( + schema="http://adaptivecards.io/schemas/adaptive-card.json", + body=[ + TextInput(id="name").with_label("Name").with_value("John Doe"), + TextInput(id="email", label="Email", value="john@contoso.com"), + ToggleInput(title="Subscribe to newsletter").with_id("subscribe").with_value("false"), + ActionSet( + actions=[ + ExecuteAction(title="Save") + .with_data({"action": "save_profile", "entity_id": "12345"}) + .with_associated_inputs("auto"), + ] + ), + ], +) +``` -The following code shows an example of `signin` action type in C#: +When the user submits the card, the handler receives the input values merged with the action data: -```csharp -var button = new CardAction() -{ - Type = ActionTypes.Signin, - Title = "Click me for signin", - Value = "https://signin.com" -}; +```text +data["action"] → "save_profile" +data["entity_id"] → "12345" +data["name"] → "John Doe" +data["email"] → "john@contoso.com" +data["subscribe"] → "true" ``` -# [JavaScript/Node.js](#tab/javascript) +# [JSON](#tab/json4) -The following code shows an example of `signin` action type in JavaScript: +The following code shows an example of associating data with card actions in JSON: -```javascript -CardFactory.actions([ +```json { - type: "signin", - title: "Click me for signin", - value: "https://signin.com" -}]) + "type": "AdaptiveCard", + "version": "1.5", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "body": [ + { + "type": "TextBlock", + "text": "User Profile", + "weight": "Bolder", + "size": "Large" + }, + { + "type": "Input.Text", + "id": "name", + "label": "Name", + "value": "John Doe" + }, + { + "type": "Input.Text", + "id": "email", + "label": "Email", + "value": "john@contoso.com" + }, + { + "type": "Input.Toggle", + "id": "subscribe", + "title": "Subscribe to newsletter", + "value": "false" + } + ], + "actions": [ + { + "type": "Action.Execute", + "title": "Save", + "data": { + "action": "save_profile", + "entity_id": "12345" + }, + "associatedInputs": "auto" + } + ] +} ``` --- -## Adaptive Cards actions - -> [!IMPORTANT] -> This documentation is considered legacy. For comprehensive information and resources related to Adaptive Cards, visit the [Adaptive Cards documentation hub](https://adaptivecards.microsoft.com/). - -Adaptive Cards support the following six action types: +### Input validation -* [Action.OpenUrl](https://adaptivecards.microsoft.com/?topic=Action.OpenUrl): Opens the specified url. -* [Action.Submit](https://adaptivecards.microsoft.com/?topic=Action.Submit): Sends the result of the submit action to the bot. -* [Action.ShowCard](https://adaptivecards.microsoft.com/?topic=Action.ShowCard): Invokes a dialog and renders the sub-card into that dialog. You only need to handle this if `ShowCardActionMode` is set to popup. -* [Action.ToggleVisibility](https://adaptivecards.microsoft.com/?topic=Action.ToggleVisibility): Shows or hides one or more elements in the card. -* [Action.Execute](/adaptive-cards/authoring-cards/universal-action-model#actionexecute): Gathers the input fields, merges with optional data field, and sends an event to the client. -* [Action.ResetInputs](dynamic-search.md#actionresetinputs): Resets the values of the inputs in an Adaptive Card. +Input controls provide built-in validation. For more information, see the Adaptive Cards [input validation documentation](https://adaptivecards.microsoft.com/?topic=input-validation). -### Action.Submit +# [C#](#tab/csharp6) -`Action.Submit` type is used to gather the input, combine the `data` properties, and send an event to the bot. When a user selects the submit action, Teams sends a message activity to the bot, which includes the user's input in key-value pairs for all input fields and hidden data that is defined in the card payload. +The following code shows an example of input validation in C#: -In the Adaptive Card schema, the `data` property for Action.Submit is either a `string` or an `object`. A submit action behaves differently for each data property: - -* `string`: A string submit action automatically sends a message from the user to the bot and is visible in the conversation history. -* `object`: An object submit action automatically sends an invisible message from the user to the bot that contains hidden data. An object submit action populates the activity’s value property while the text property is empty. +```csharp +using Microsoft.Teams.Cards; -Action.Submit is equivalent to the Bot Framework actions. You can also modify the Adaptive Card `Action.Submit` payload to support existing Bot Framework actions using a `msteams` property in the `data` object of `Action.Submit`. When you define the `msteams` property under `data`, the Teams client defines the behavior of `Action.Submit`. If the `msteams` property isn't defined in the schema, `Action.Submit` works like a regular Bot Framework invoke action, where; the submit action triggers an invoke call to the bot and the bot receives the payload with all the input values defined in the input fields. +private static AdaptiveCard CreateProfileCardWithValidation() +{ + return new AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Profile with Validation") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large + }, + new NumberInput + { + Id = "age", + Label = "Age", + IsRequired = true, + Min = 0, + Max = 120 + }, + new TextInput + { + Id = "name", + Label = "Name", + IsRequired = true, + ErrorMessage = "Name is required" + }, + new TextInput + { + Id = "location", + Label = "Location" + } + }, + Actions = new List + { + new ExecuteAction + { + Title = "Save", + Data = new Union(new SubmitActionData + { + NonSchemaProperties = new Dictionary + { + { "action", "save_profile" } + } + }), + AssociatedInputs = AssociatedInputs.Auto + } + } + }; +} +``` -> [!NOTE] -> ->* The bot doesn’t receive user input unless the user submits their actions in the Adaptive Card through a button, such as **Save** or **Submit**. For example, the bot doesn't consider user actions, such as selecting an option from multiple choices or filling out fields in a form, as inputs unless the user submits them. ->* Adding `msteams` to data with a Bot Framework action doesn't work with an Adaptive Card dialog. ->* Primary or destructive `ActionStyle` isn't supported in Teams. ->* Your app has five seconds to respond to the invoke message. +# [TypeScript](#tab/typescript6) + +The following code shows an example of input validation in TypeScript: + +```typescript +import { + AdaptiveCard, + NumberInput, + TextInput, + ActionSet, + ExecuteAction, +} from '@microsoft/teams.cards'; + +function createProfileCardInputValidation() { + const ageInput = new NumberInput({ id: 'age' }) + .withLabel('Age') + .withIsRequired(true) + .withMin(0) + .withMax(120); + + const nameInput = new TextInput({ id: 'name' }) + .withLabel('Name') + .withIsRequired() + .withErrorMessage('Name is required'); + + const card = new AdaptiveCard( + ageInput, + nameInput, + new TextInput({ id: 'location' }).withLabel('Location'), + new ActionSet( + new ExecuteAction({ title: 'Save' }) + .withData({ action: 'save_profile' }) + .withAssociatedInputs('auto') + ) + ); + + return card; +} +``` -#### Example +# [Python](#tab/python6) + +The following code shows an example of input validation in Python: + +```python +from microsoft_teams.cards import AdaptiveCard, ActionSet, ExecuteAction, NumberInput, TextInput + +def create_profile_card_input_validation(): + age_input = NumberInput(id="age").with_label("Age").with_is_required(True).with_min(0).with_max(120) + name_input = TextInput(id="name").with_label("Name").with_is_required(True).with_error_message("Name is required") + + card = AdaptiveCard( + schema="http://adaptivecards.io/schemas/adaptive-card.json", + body=[ + age_input, + name_input, + TextInput(id="location").with_label("Location"), + ActionSet( + actions=[ + ExecuteAction(title="Save") + .with_data({"action": "save_profile"}) + .with_associated_inputs("auto") + ] + ), + ], + ) + return card +``` -The following is an example of an `Action.Submit` card payload: +# [JSON](#tab/json5) -The payload consists of a text input field `"id": "text-1"` and hidden data payload `"hiddenKey": 123.45`. +The following code shows an example of input validation in JSON: ```json { - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "type": "AdaptiveCard", "version": "1.5", - "fallbackText": "fallback text for sample 01", - "speak": "This is Adaptive Card sample 1", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "body": [ { - "type": "Container", - "items": [ - { - "id": "text-1", - "type": "Input.Text" - } - ] + "type": "TextBlock", + "text": "Profile with Validation", + "weight": "Bolder", + "size": "Large" + }, + { + "type": "Input.Number", + "id": "age", + "label": "Age", + "isRequired": true, + "min": 0, + "max": 120 + }, + { + "type": "Input.Text", + "id": "name", + "label": "Name", + "isRequired": true, + "errorMessage": "Name is required" + }, + { + "type": "Input.Text", + "id": "location", + "label": "Location" } ], "actions": [ { - "type": "Action.Submit", + "type": "Action.Execute", + "title": "Save", "data": { - "hiddenKey": 123.45 - } + "action": "save_profile" + }, + "associatedInputs": "auto" } ] } ``` -:::image type="content" source="../../assets/images/adaptive-cards/adaptive-card-action-submit.png" alt-text="Screenshot shows an example of an Adaptive Card with the submit button."::: - -The following is an example of the incoming activity to a bot when user types something in the input field and selects **Submit**. The `value` attribute includes the user's input in the `text-1` property and a hidden data payload in the `hiddenKey` property: - - ```json - -{ - "type": "message", - "timestamp": "2023-07-18T23:45:41.699Z", - "localTimestamp": "2023-07-18T16:45:41.699-07:00", - "id": "f:9eb18f56-2259-8fa4-7dfc-111ffff58e67", - "channelId": "msteams", - "serviceUrl": "https://smba.trafficmanager.net/amer/", - "from": { - "id": "29:1E0NZYNZFQOCUI8zM9NY_EhlCsWgNbLGTHUNdBVX2ob8SLjhltEhQMPi07Gr6MLScFeS8SrKH1WGvJSiVKThnyw", - "name": "Megan Bowen", - "aadObjectId": "97b1ec61-45bf-453c-9059-6e8984e0cef4" - }, - "conversation": { - "conversationType": "personal", - "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", - "id": "a:1H-RowZ3FrIheyjTupPnoCC6JvOLB5pCWms1xwqvAJG97j61D18EuSennYZE6tyfbQrnfIN3uIcwpOx73mg10hHp_uoTMMQlXhXosIu_q7QVCaYiW6Ch3bPWAitUw4aSX" - }, - "recipient": { - "id": "28:159e1c0f-15ef-4597-a8c6-44ba1fd89b78", - "name": "Mushroom" - }, - "entities": [ - { - "locale": "en-US", - "country": "US", - "platform": "Web", - "timezone": "America/Los_Angeles", - "type": "clientInfo" - } - ], - "channelData": { - "tenant": { - "id": "72f988bf-86f1-41af-91ab-2d7cd011db47" - }, - "source": { - "name": "message" - }, - "legacy": { - "replyToId": "1:1XFuAl7wF96vl6iAQk9tqus0uFrB89uujGpld-Qm-XEw" - } - }, - "replyToId": "1689723936016", - "value": { - "hiddenKey": 123.45, - "text-1": "HELLO" - }, - "locale": "en-US", - "localTimezone": "America/Los_Angeles" -} - ``` +--- ### Conditional enablement of action buttons @@ -548,8 +681,8 @@ You can use the `conditionallyEnabled` property to disable action buttons until Here's how the `conditionallyEnabled` property is defined: -| Property| Type | Required | Description | -|-----------|------|----------|-------------| +| Property | Type | Required | Description | +| --- | --- | --- | --- | | `conditionallyEnabled` | Boolean | ✔️ | Controls if the action is enabled only if at least one required input has been filled by the user. | The following card payload shows a conditionally enabled button: @@ -576,12 +709,12 @@ The following card payload shows a conditionally enabled button: ], "actions": [ { - "type": "Action.Submit", + "type": "Action.Execute", "title": "Submit", "conditionallyEnabled": true }, { - "type": "Action.Submit", + "type": "Action.Execute", "title": "Permanently disabled button", "isEnabled": false } @@ -608,210 +741,465 @@ The following card payload shows a conditionally enabled button: :::row-end::: -#### Form completion feedback +## Server handlers -You can build form completion feedback using an Adaptive Card. Form completion message appears in Adaptive Cards while sending a response to the bot. The message can be of two types, error or success: +Universal Action submissions are delivered to your bot as `invoke` activities named `adaptiveCard/action`. In the Teams SDK, these are surfaced to your app as `card.action` activities, which give you access to the validated input values plus any `data` values you configured to be sent back to you. -* **Error**: When a response sent to the bot is unsuccessful, **Something went wrong, Try again** message appears. The error occurs due to various reasons, such as: - * Too many requests - * Multiple concurrent operations on the same conversation - * Service dependency issue - * Gateway Timeout +Use the `OnAdaptiveCardAction` handler to process card actions: - :::image type="content" source="../../assets/images/Cards/error-message.png" alt-text="Screenshot shows an Error message in an Adaptive Card." ::: +# [C#](#tab/csharp7) -* **Success**: When a response sent to the bot is successful, **Your response was sent to the app** message appears. +```csharp +using System.Text.Json; +using Microsoft.Teams.Api.Activities.Invokes.AdaptiveCards; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Common.Logging; - :::image type="content" source="../../assets/images/Cards/success.PNG" alt-text="Screenshot shows a success message in an Adaptive Card."::: +//... - You can select **Close** or switch chat to dismiss the message. +teams.OnAdaptiveCardAction(async context => +{ + var activity = context.Activity; + context.Log.Info("[CARD_ACTION] Card action received"); - If you don't want to display the success message, set the attribute `hide` to `true` in the `msTeams` `feedback` property. Following is an example: + var data = activity.Value?.Action?.Data; - ```json - "content": { - "type": "AdaptiveCard", - "title": "Card with hidden footer messages", - "version": "1.0", - "actions": [ - { - "type": "Action.Submit", - "title": "Submit", - "msTeams": { - "feedback": { - "hide": true - } - } - } - ] - } - ``` + context.Log.Info($"[CARD_ACTION] Raw data: {JsonSerializer.Serialize(data)}"); -For more information on cards and cards in bots, see [cards documentation](~/task-modules-and-cards/what-are-cards.md). + if (data == null) + { + context.Log.Error("[CARD_ACTION] No data in card action"); + return new ActionResponse.Message("No data specified") { StatusCode = 400 }; + } -### Adaptive Cards with messageBack action + string? action = data.TryGetValue("action", out var actionObj) ? actionObj?.ToString() : null; -To include a `messageBack` action with an Adaptive Card, include the following details in the `msteams` object: + if (string.IsNullOrEmpty(action)) + { + context.Log.Error("[CARD_ACTION] No action specified in card data"); + return new ActionResponse.Message("No action specified") { StatusCode = 400 }; + } + context.Log.Info($"[CARD_ACTION] Processing action: {action}"); -> [!NOTE] -> You can include additional hidden properties in the `data` object, if required. + string? GetFormValue(string key) + { + if (data.TryGetValue(key, out var val)) + { + if (val is JsonElement element) + return element.GetString(); + return val?.ToString(); + } + return null; + } -| Property | Description | -| --- | --- | -| `type` | Set to `messageBack`. | -| `displayText` | Optional. Used by the user in the chat stream when the action is performed. This text isn't sent to your bot. | -| `value` | Sent to your bot when the action is performed. You can encode context for the action, such as unique identifiers or a JSON object. | -| `text` | Sent to your bot when the action is performed. Use this property to simplify bot development. Your code can check a single top-level property to dispatch bot logic. | + switch (action) + { + case "submit_feedback": + var feedbackText = GetFormValue("feedback") ?? "No feedback provided"; + await context.Send($"Feedback received: {feedbackText}"); + break; + + case "create_task": + var title = GetFormValue("title") ?? "Untitled"; + var priority = GetFormValue("priority") ?? "medium"; + var dueDate = GetFormValue("due_date") ?? "No date"; + await context.Send($"Task created!\nTitle: {title}\nPriority: {priority}\nDue: {dueDate}"); + break; + + case "save_profile": + var name = GetFormValue("name") ?? "Unknown"; + var email = GetFormValue("email") ?? "No email"; + var subscribe = GetFormValue("subscribe") ?? "false"; + await context.Send($"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}"); + break; + + default: + context.Log.Error($"[CARD_ACTION] Unknown action: {action}"); + return new ActionResponse.Message("Unknown action") { StatusCode = 400 }; + } -The following code shows an example of Adaptive Cards with `messageBack` action: + return new ActionResponse.Message("Action processed successfully") { StatusCode = 200 }; +}); +``` -```json -{ - "type": "Action.Submit", - "title": "Click me for messageBack", - "data": { - "msteams": { - "type": "messageBack", - "displayText": "I clicked this button", - "text": "text to bots", - "value": "{\"bfKey\": \"bfVal\", \"conflictKey\": \"from value\"}" - } +# [TypeScript](#tab/typescript7) + +```typescript +import { + AdaptiveCardActionErrorResponse, + AdaptiveCardActionMessageResponse, +} from '@microsoft/teams.api'; +import { App } from '@microsoft/teams.apps'; + +// ... + +app.on('card.action', async ({ activity, send }) => { + const data = activity.value?.action?.data; + + if (!data?.action) { + return { + statusCode: 400, + type: 'application/vnd.microsoft.error', + value: { + code: 'BadRequest', + message: 'No action specified', + innerHttpError: { + statusCode: 400, + body: { error: 'No action specified' }, + }, + }, + } satisfies AdaptiveCardActionErrorResponse; } -} + + console.debug('Received action data:', data); + + switch (data.action) { + case 'submit_feedback': + await send(`Feedback received: ${data.feedback}`); + break; + + case 'create_task': + await send( + `Task created!\nTitle: ${data.title}\nPriority: ${data.priority}\nDue: ${data.due_date}` + ); + break; + + case 'save_profile': + await send( + `Profile saved!\nName: ${data.name}\nEmail: ${data.email}\nSubscribed: ${data.subscribe}` + ); + break; + + default: + return { + statusCode: 400, + type: 'application/vnd.microsoft.error', + value: { + code: 'BadRequest', + message: 'Unknown action', + innerHttpError: { + statusCode: 400, + body: { error: 'Unknown action' }, + }, + }, + } satisfies AdaptiveCardActionErrorResponse; + } + + return { + statusCode: 200, + type: 'application/vnd.microsoft.activity.message', + value: 'Action processed successfully', + } satisfies AdaptiveCardActionMessageResponse; +}); ``` -### Adaptive Cards with imBack action +# [Python](#tab/python7) + +```python +from microsoft_teams.api import ( + AdaptiveCardInvokeActivity, + AdaptiveCardActionErrorResponse, + AdaptiveCardActionMessageResponse, + HttpError, + InnerHttpError, + AdaptiveCardInvokeResponse, +) +from microsoft_teams.apps import ActivityContext + +# ... + +@app.on_card_action +async def handle_card_action( + ctx: ActivityContext[AdaptiveCardInvokeActivity], +) -> AdaptiveCardInvokeResponse: + data = ctx.activity.value.action.data + + if not data.get("action"): + return AdaptiveCardActionErrorResponse( + status_code=400, + type="application/vnd.microsoft.error", + value=HttpError( + code="BadRequest", + message="No action specified", + inner_http_error=InnerHttpError( + status_code=400, + body={"error": "No action specified"}, + ), + ), + ) + + print("Received action data:", data) + + if data["action"] == "submit_feedback": + await ctx.send(f"Feedback received: {data.get('feedback')}") + elif data["action"] == "create_task": + await ctx.send( + f"Task created!\nTitle: {data.get('title')}\nPriority: {data.get('priority')}\nDue: {data.get('due_date')}" + ) + elif data["action"] == "save_profile": + await ctx.send( + f"Profile saved!\nName: {data.get('name')}\nEmail: {data.get('email')}\nSubscribed: {data.get('subscribe')}" + ) + else: + return AdaptiveCardActionErrorResponse( + status_code=400, + type="application/vnd.microsoft.error", + value=HttpError( + code="BadRequest", + message="Unknown action", + inner_http_error=InnerHttpError( + status_code=400, + body={"error": "Unknown action"}, + ), + ), + ) + + return AdaptiveCardActionMessageResponse( + status_code=200, + type="application/vnd.microsoft.activity.message", + value="Action processed successfully", + ) +``` -To include an `imBack` action with an Adaptive Card, include the following details in the `msteams` object: +--- > [!NOTE] -> The `value` field is a simple string that doesn’t support formatting or hidden characters. +> The `data` values come from JSON and need to be extracted using the helper method shown above to handle different JSON element types. -| Property | Description | -| --- | --- | -| `type` | Set to `imBack`. | -| `value` | String that needs to be echoed back in the chat. | +## End-to-end example: Task form card -The following code shows an example of Adaptive Cards with `imBack` action: +The following example shows a complete card with input fields and an action handler. -```json +### Build the card + +# [C#](#tab/csharp8) + +```csharp +using Microsoft.Teams.Cards; + +private static AdaptiveCard CreateTaskFormCard() { - "type": "Action.Submit", - "title": "Click me for imBack", - "data": { - "msteams": { - "type": "imBack", - "value": "Text to reply in chat" - } - } + return new AdaptiveCard + { + Schema = "http://adaptivecards.io/schemas/adaptive-card.json", + Body = new List + { + new TextBlock("Create New Task") + { + Weight = TextWeight.Bolder, + Size = TextSize.Large + }, + new TextInput + { + Id = "title", + Label = "Task Title", + Placeholder = "Enter task title" + }, + new TextInput + { + Id = "description", + Label = "Description", + Placeholder = "Enter task details", + IsMultiline = true + }, + new ChoiceSetInput + { + Id = "priority", + Label = "Priority", + Value = "medium", + Choices = new List + { + new() { Title = "High", Value = "high" }, + new() { Title = "Medium", Value = "medium" }, + new() { Title = "Low", Value = "low" } + } + }, + new DateInput + { + Id = "due_date", + Label = "Due Date", + Value = DateTime.Now.ToString("yyyy-MM-dd") + } + }, + Actions = new List + { + new ExecuteAction + { + Title = "Create Task", + Data = new Union(new SubmitActionData + { + NonSchemaProperties = new Dictionary + { + { "action", "create_task" } + } + }), + AssociatedInputs = AssociatedInputs.Auto, + Style = ActionStyle.Positive + } + } + }; } ``` -### Adaptive Cards with sign-in action +# [TypeScript](#tab/typescript8) + +```typescript +import { + AdaptiveCard, + TextBlock, + TextInput, + ChoiceSetInput, + DateInput, + ActionSet, + ExecuteAction, +} from '@microsoft/teams.cards'; + +function createTaskFormCard() { + return new AdaptiveCard( + new TextBlock('Create New Task', { + size: 'Large', + weight: 'Bolder', + }), + new TextInput({ id: 'title' }) + .withLabel('Task Title') + .withPlaceholder('Enter task title'), + new TextInput({ id: 'description' }) + .withLabel('Description') + .withPlaceholder('Enter task details') + .withIsMultiline(true), + new ChoiceSetInput( + { title: 'High', value: 'high' }, + { title: 'Medium', value: 'medium' }, + { title: 'Low', value: 'low' } + ) + .withId('priority') + .withLabel('Priority') + .withValue('medium'), + new DateInput({ id: 'due_date' }) + .withLabel('Due Date') + .withValue(new Date().toISOString().split('T')[0]), + new ActionSet( + new ExecuteAction({ title: 'Create Task' }) + .withData({ action: 'create_task' }) + .withAssociatedInputs('auto') + .withStyle('positive') + ) + ); +} +``` -To include a `signin` action with an Adaptive Card, include the following details in the `msteams` object: +# [Python](#tab/python8) + +```python +from datetime import datetime +from microsoft_teams.cards import ( + AdaptiveCard, TextBlock, ActionSet, ExecuteAction, + Choice, ChoiceSetInput, DateInput, TextInput, +) + +def create_task_form_card(): + return AdaptiveCard( + schema="http://adaptivecards.io/schemas/adaptive-card.json", + body=[ + TextBlock(text="Create New Task", weight="Bolder", size="Large"), + TextInput(id="title") + .with_label("Task Title") + .with_placeholder("Enter task title"), + TextInput(id="description") + .with_label("Description") + .with_placeholder("Enter task details") + .with_is_multiline(True), + ChoiceSetInput(choices=[ + Choice(title="High", value="high"), + Choice(title="Medium", value="medium"), + Choice(title="Low", value="low"), + ]).with_id("priority").with_label("Priority").with_value("medium"), + DateInput(id="due_date") + .with_label("Due Date") + .with_value(datetime.now().strftime("%Y-%m-%d")), + ActionSet( + actions=[ + ExecuteAction(title="Create Task") + .with_data({"action": "create_task"}) + .with_associated_inputs("auto") + .with_style("positive") + ] + ), + ], + ) +``` -> [!NOTE] -> You can include additional hidden properties in the `data` object, if required. +--- -| Property | Description | -| --- | --- | -| `type` | Set to `signin`. | -| `value` | Set to the URL where you want to redirect. | +### Send the card -The following code shows an example of Adaptive Cards with `signin` action: +# [C#](#tab/csharp9) -```json +```csharp +teams.OnMessage(async context => { - "type": "Action.Submit", - "title": "Click me for signin", - "data": { - "msteams": { - "type": "signin", - "value": "https://signin.com" + var text = context.Activity.Text?.ToLowerInvariant() ?? ""; + + if (text.Contains("form")) + { + await context.Typing(); + var card = CreateTaskFormCard(); + await context.Send(card); } - } -} +}); ``` -### Adaptive Cards with invoke action +# [TypeScript](#tab/typescript9) -To include an `invoke` action with an Adaptive Card, include the following details in the `msteams` object: +```typescript +import { App } from '@microsoft/teams.apps'; -> [!NOTE] -> You can include additional hidden properties in the `data` object, if required. +app.on('message', async ({ send, activity }) => { + const text = activity.text?.toLowerCase() ?? ''; -| Property | Description | -| --- | --- | -| `type` | Set to `task/fetch`. | -| `data` | Set the value. | - -The following code shows an example of Adaptive Cards with `invoke` action: - -```json -{ - "type": "Action.Submit", - "title": "submit", - "data": { - "msteams": { - "type": "task/fetch" - } + if (text.includes('form')) { + await send({ type: 'typing' }); + const card = createTaskFormCard(); + await send(card); } -} +}); ``` -| Property | Description | -| --- | --- | -| `type` | Set to `invoke`. | -| `value` | Set the value to display. | +# [Python](#tab/python9) -The following code shows an example of Adaptive Cards with `invoke` action with additional payload data: +```python +from microsoft_teams.api import MessageActivity, TypingActivityInput +from microsoft_teams.apps import ActivityContext -```json -[ - { - "type": "Action.Submit", - "title": "submit with object value", - "data": { - "ab": "xy", - "msteams": { - "type": "invoke", - "value": { "a": "b" } - } - } - }, - { - "type": "Action.Submit", - "title": "submit with stringified json value", - "data": { - "ab": "xy", - "msteams": { - "type": "invoke", - "value": "{ \"a\": \"b\"}" - } - } - } -] +@app.on_message +async def handle_message(ctx: ActivityContext[MessageActivity]): + text = (ctx.activity.text or "").lower() + + if "form" in text: + await ctx.reply(TypingActivityInput()) + card = create_task_form_card() + await ctx.send(card) ``` +--- + ## Code samples |S.No.|Card| Description|.NET|Node.js|Python|Java|Manifest| |:--|:--|:--------------------------------------------------------|-----|------------|-----|----------------------------|------| -|1|Adaptive Card actions|This sample shows how to send Adaptive Cards with multiple action types using a Teams bot.|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-adaptive-card-actions/csharp)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-adaptive-card-actions/nodejs)|NA|NA|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/bot-adaptive-card-actions/csharp/demo-manifest/bot-adaptivecard-actions.zip)| -|2|Using cards|Introduces all card types including thumbnail, audio, media etc. Builds on Welcoming user + multi-prompt bot by presenting a card with buttons in welcome message that route to appropriate dialog.|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/06.using-cards)|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/06.using-cards)|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/python/06.using-cards)|NA|NA| -|3|Adaptive Cards|Demonstrates how the multi-turn dialog can use a card to get user input for name and age.|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/csharp_dotnetcore/07.using-adaptive-cards)|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/07.using-adaptive-cards)|[View](https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/python/07.using-adaptive-cards)|NA|NA| -|4|Card Formatting|This sample demonstrates a conditionally enabled button.|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-formatting-cards/csharp)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-formatting-cards/nodejs)|NA|NA|NA| +|1|Bot Cards|This sample shows how to use the Teams SDK to send and handle various card types, including Adaptive Cards with interactive actions, in Microsoft Teams.|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-cards/dotnet/bot-cards)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-cards/nodejs/bot-cards)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-cards/python/bot-cards)|NA|NA| +|2|Card Formatting|This sample demonstrates how to use the `conditionallyEnabled` property with `Action.Execute` to disable action buttons until required inputs are filled.|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-cards/dotnet/bot-cards)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-cards/nodejs/bot-cards)|NA|NA|NA| ## Next step > [!div class="nextstepaction"] -> [Universal Actions for Adaptive Cards](../cards/Universal-actions-for-adaptive-cards/Overview.md) +> [Building Adaptive Cards](/microsoftteams/platform/teams-sdk/in-depth-guides/adaptive-cards/building-adaptive-cards) ## See also -* [Cards reference](./cards-reference.md) -* [Types of cards](cards-reference.md) -* [Use dialogs from bots](~/task-modules-and-cards/task-modules/task-modules-bots.md) -* [Adaptive Cards in bots](../../bots/how-to/conversations/conversation-messages.md#adaptive-cards) -* [Adaptive Card-based Loop component](../../m365-apps/cards-loop-component.md) +* [Adaptive Cards overview](/microsoftteams/platform/teams-sdk/in-depth-guides/adaptive-cards/overview) +* [Executing Actions](/microsoftteams/platform/teams-sdk/in-depth-guides/adaptive-cards/executing-actions) +* [Listening to Activities](/microsoftteams/platform/teams-sdk/essentials/on-activity/overview) +* [Adaptive Cards documentation](https://adaptivecards.microsoft.com/) +* [Adaptive Cards Designer](https://adaptivecards.microsoft.com/designer.html) diff --git a/msteams-platform/task-modules-and-cards/task-modules/task-modules-bots.md b/msteams-platform/task-modules-and-cards/task-modules/task-modules-bots.md index ff6161e724e..4f521fd709b 100644 --- a/msteams-platform/task-modules-and-cards/task-modules/task-modules-bots.md +++ b/msteams-platform/task-modules-and-cards/task-modules/task-modules-bots.md @@ -3,7 +3,7 @@ title: Use dialogs in Microsoft Teams bots description: Learn how to use dialogs with Microsoft Teams bots and invoke dialogs, about Bot Framework card and Adaptive Card actions, deep links, and respond to messages. ms.localizationpriority: medium ms.topic: how-to -ms.date: 01/31/2023 +ms.date: 04/10/2026 --- # Use dialogs with bots @@ -12,8 +12,8 @@ Invoke dialogs (referred as task modules in TeamsJS v1.x) from Microsoft Teams b There are two ways of invoking dialogs: -* A new invoke message `task/fetch`: Using the `invoke` [card action](~/task-modules-and-cards/cards/cards-actions.md#action-type-invoke) for Bot Framework cards, or the `Action.Submit` [card action](~/task-modules-and-cards/cards/cards-actions.md#adaptive-cards-actions) for Adaptive Cards, with `task/fetch`, either an HTML or Adaptive Card-based dialog is fetched dynamically from your bot. -* Deep link URLs: Using the [deep link syntax for dialogs](../../concepts/build-and-test/deep-link-application.md#deep-link-to-open-a-dialog), you can use the `openUrl` [card action](~/task-modules-and-cards/cards/cards-actions.md#action-type-openurl) for Bot Framework cards or the `Action.OpenUrl` [card action](~/task-modules-and-cards/cards/cards-actions.md#adaptive-cards-actions) for Adaptive Cards, respectively. With deep link URLs, the dialog URL or Adaptive Card body is already known to avoid a server round-trip relative to `task/fetch`. +* A new invoke message `task/fetch`: Using the [`Action.Execute`](~/task-modules-and-cards/cards/cards-actions.md#actionexecute) card action for Adaptive Cards with `task/fetch`, either an HTML or Adaptive Card-based dialog is fetched dynamically from your bot. +* Deep link URLs: Using the [deep link syntax for dialogs](../../concepts/build-and-test/deep-link-application.md#deep-link-to-open-a-dialog), you can use the [`Action.OpenUrl`](~/task-modules-and-cards/cards/cards-actions.md#actionopenurl) card action for Adaptive Cards. With deep link URLs, the dialog URL or Adaptive Card body is already known to avoid a server round-trip relative to `task/fetch`. > [!IMPORTANT] > Each `url` and `fallbackUrl` must implement the HTTPS encryption protocol. @@ -28,9 +28,9 @@ When the `value` object of the `invoke` card action or `Action.Submit` is initia The following steps provide instructions on how to invoke a dialog (referred as task module in TeamsJS v1.x) using `task/fetch`: -1. This image shows a Bot Framework hero card with a **Buy** `invoke` [card action](~/task-modules-and-cards/cards/cards-actions.md#action-type-invoke). The value of the `type` property is `task/fetch` and the rest of the `value` object can be of your choice. -1. The bot receives the `invoke` HTTP POST message. -1. The bot creates a response object and returns it in the body of the POST response with an HTTP 200 response code. For more information on schema for responses, see the [discussion on task/submit](#responds-to-the-tasksubmit-messages). The following code provides an example of body of the HTTP response that contains a [TaskInfo object](~/task-modules-and-cards/task-modules/invoking-task-modules.md#dialoginfo-object) embedded in a wrapper object: +1. This image shows an Adaptive Card with a **Buy** [`Action.Execute`](~/task-modules-and-cards/cards/cards-actions.md#actionexecute) card action. The value of the `type` property is `task/fetch` and the rest of the `data` object can be of your choice. +1. The bot receives a `card.action` activity. In the Teams SDK, you handle this using the `OnAdaptiveCardAction` handler. For more information, see [Executing Actions](/microsoftteams/platform/teams-sdk/in-depth-guides/adaptive-cards/executing-actions). +1. The bot creates an `ActionResponse` object and returns it. For more information on schema for responses, see the [discussion on task/submit](#responds-to-the-tasksubmit-messages). The following code provides an example of the response body that contains a [TaskInfo object](~/task-modules-and-cards/task-modules/invoking-task-modules.md#dialoginfo-object) embedded in a wrapper object: ```json { diff --git a/msteams-platform/whats-new.md b/msteams-platform/whats-new.md index b66f71b4894..c3cbc6518bb 100644 --- a/msteams-platform/whats-new.md +++ b/msteams-platform/whats-new.md @@ -260,7 +260,7 @@ Teams platform features that are available to all app developers.
| 07/21/2022 | Introduced step by step guide to send activity feed notifications | Design your app > UI components> Activity feed notifications > [Send activity feed notification](sbs-graphactivity-feedbroadcast.yml) | | 07/08/2022| Updates to send channel ID selected by user during app installation to bots via conversation and installation update events | Build bots > Bot conversations > Conversation events in your Teams bot > [Conversation events in your Teams bot](bots/how-to/conversations/subscribe-to-conversation-events.md) | | 06/16/2022 | Updated media capabilities to support desktop and mobile| Integrate device capabilities > [Integrate media capabilities](concepts/device-capabilities/media-capabilities.md)| -| 06/08/2022 | Optional card feedback for success message| Build cards and task modules > Build cards > [Form completion feedback](task-modules-and-cards/cards/cards-actions.md#form-completion-feedback)| +| 06/08/2022 | Server handlers for card actions| Build cards and task modules > Build cards > [Server handlers](task-modules-and-cards/cards/cards-actions.md#server-handlers)| | 06/03/2022 | Updated Add authentication module for enabling SSO for tab app with new structure and procedures | Add authentication > Tabs > [Enable single sign-on in a tab app](tabs/how-to/authentication/tab-sso-overview.md) | | 05/24/2022 | Additional tips for rapid approval to publish your app linked to a SaaS offer | Publish to the Teams Store > Overview > [Additional tips for rapid approval to publish your app linked to a SaaS offer](~/concepts/deploy-and-publish/appsource/publish.md#additional-tips-for-rapid-approval-to-publish-your-app-linked-to-a-saas-offer) | | 05/24/2022 | Submit your Outlook- and Office-enabled apps to the Teams Store | Extend your app across Microsoft 365 > [Overview](m365-apps/overview.md) | @@ -296,7 +296,7 @@ Teams platform features that are available to all app developers.
| 02/08/2022 | Introduced step-by-step guide to create Calling and Meeting bot| Build bots > Calls and meetings bots > Register calls and meetings bot > [Step-by-step guide to create Calling and Meeting bot](sbs-calling-and-meeting.yml) | | 02/02/2022 | Introduced app manifest version 1.12 | App manifest > [App manifest schema](/microsoft-365/extensibility/schema/#all-generally-available-versions) | | 01/25/2022 | Send real-time captions API | Build apps for Teams meetings > Meeting apps API references> [Advanced meeting APIs](apps-in-teams-meetings/meeting-apps-apis.md)| -| 01/19/2022 | Adaptive Cards form completion feedback | Build cards and task modules > Build cards > [Form completion feedback](task-modules-and-cards/cards/cards-actions.md#form-completion-feedback)| +| 01/19/2022 | Server handlers for Adaptive Cards form completion | Build cards and task modules > Build cards > [Server handlers](task-modules-and-cards/cards/cards-actions.md#server-handlers)| | 01/17/2022 | People Picker in Adaptive Cards for desktop | Build cards and task modules > Build cards > [People Picker in Adaptive Cards](task-modules-and-cards/cards/people-picker.md)|