Skip to main content

Action commands

Action commands allow you to present your users with a modal pop-up called a dialog in Teams. The dialog collects or displays information, processes the interaction, and sends the information back to Teams compose box.

Action command invocation locations​

There are three different areas action commands can be invoked from:

  1. Compose Area
  2. Compose Box
  3. Message

Compose Area and Box​

compose area and box

Message action command​

message action command

tip

See the Invoke Locations guide to learn more about the different entry points for action commands.

Setting up your Teams app manifest​

To use action commands you have define them in the Teams app manifest. Here is an example:

"composeExtensions": [
{
"botId": "${{BOT_ID}}",
"commands": [
{
"id": "createCard",
"type": "action",
"context": [
"compose",
"commandBox"
],
"description": "Command to run action to create a card from the compose box.",
"title": "Create Card",
"parameters": [
{
"name": "title",
"title": "Card title",
"description": "Title for the card",
"inputType": "text"
},
{
"name": "subTitle",
"title": "Subtitle",
"description": "Subtitle for the card",
"inputType": "text"
},
{
"name": "text",
"title": "Text",
"description": "Text for the card",
"inputType": "textarea"
}
]
},
{
"id": "getMessageDetails",
"type": "action",
"context": [
"message"
],
"description": "Command to run action on message context.",
"title": "Get Message Details"
},
{
"id": "fetchConversationMembers",
"description": "Fetch the conversation members",
"title": "Fetch Conversation Members",
"type": "action",
"fetchTask": true,
"context": [
"compose"
]
},
]
}
]

Here we are defining three different commands:

  1. createCard - that can be invoked from either the compose or commandBox areas. Upon invocation a dialog will popup asking the user to fill the title, subTitle, and text.

Parameters

  1. getMessageDetails - It is invoked from the message overflow menu. Upon invocation the message payload will be sent to the app which will then return the details like createdDate...etc.

Get Message Details Command

  1. fetchConversationMembers - It is invoked from the compose area. Upon invocation the app will return an adaptive card in the form of a dialog with the conversation roster.

Fetch conversation members

Handle submission​

Handle submission when the createCard or getMessageDetails actions commands are invoked.


@app.on_message_ext_submit
async def handle_message_ext_submit(ctx: ActivityContext[MessageExtensionSubmitActionInvokeActivity]):
command_id = ctx.activity.value.command_id

if command_id == "createCard":
card = create_card(ctx.activity.value.data or {})
elif command_id == "getMessageDetails" and ctx.activity.value.message_payload:
card = create_message_details_card(ctx.activity.value.message_payload)
else:
raise Exception(f"Unknown commandId: {command_id}")

main_attachment = card_attachment(AdaptiveCardAttachment(content=card))
attachment = MessagingExtensionAttachment(
content_type=main_attachment.content_type, content=main_attachment.content
)

result = MessagingExtensionResult(
type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=[attachment]
)

return MessagingExtensionActionInvokeResponse(compose_extension=result)

create_card() method

def create_card(data: Dict[str, str]) -> AdaptiveCard:
"""Create an adaptive card from form data."""
return AdaptiveCard.model_validate(
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{"type": "Image", "url": IMAGE_URL},
{
"type": "TextBlock",
"text": data.get("title", ""),
"size": "Large",
"weight": "Bolder",
"color": "Accent",
"style": "heading",
},
{
"type": "TextBlock",
"text": data.get("subTitle", ""),
"size": "Small",
"weight": "Lighter",
"color": "Good",
},
{"type": "TextBlock", "text": data.get("text", ""), "wrap": True, "spacing": "Medium"},
],
}
)

create_message_details_card() method

def create_message_details_card(message_payload: Message) -> AdaptiveCard:
"""Create a card showing message details."""
body: List[Dict[str, Union[str, bool]]] = [
{
"type": "TextBlock",
"text": "Message Details",
"size": "Large",
"weight": "Bolder",
"color": "Accent",
"style": "heading",
}
]

if message_payload.body and message_payload.body.content:
content_blocks: List[Dict[str, Union[str, bool]]] = [
{"type": "TextBlock", "text": "Content", "size": "Medium", "weight": "Bolder", "spacing": "Medium"},
{"type": "TextBlock", "text": message_payload.body.content},
]
body.extend(content_blocks)

if message_payload.attachments:
attachment_blocks: List[Dict[str, Union[str, bool]]] = [
{"type": "TextBlock", "text": "Attachments", "size": "Medium", "weight": "Bolder", "spacing": "Medium"},
{
"type": "TextBlock",
"text": f"Number of attachments: {len(message_payload.attachments)}",
"wrap": True,
"spacing": "Small",
},
]
body.extend(attachment_blocks)

if message_payload.created_date_time:
date_blocks: List[Dict[str, Union[str, bool]]] = [
{"type": "TextBlock", "text": "Created Date", "size": "Medium", "weight": "Bolder", "spacing": "Medium"},
{"type": "TextBlock", "text": message_payload.created_date_time, "wrap": True, "spacing": "Small"},
]
body.extend(date_blocks)

if message_payload.link_to_message:
link_blocks: List[Dict[str, Union[str, bool]]] = [
{"type": "TextBlock", "text": "Message Link", "size": "Medium", "weight": "Bolder", "spacing": "Medium"}
]
body.extend(link_blocks)

actions = [{"type": "Action.OpenUrl", "title": "Go to message", "url": message_payload.link_to_message}]
else:
actions = []

return AdaptiveCard.model_validate({"type": "AdaptiveCard", "version": "1.4", "body": body, "actions": actions})

Handle opening adaptive card dialog​

Handle opening adaptive card dialog when the fetchConversationMembers command is invoked.


@app.on_message_ext_open
async def handle_message_ext_open(ctx: ActivityContext[MessageExtensionFetchTaskInvokeActivity]):
conversation_id = ctx.activity.conversation.id
members = await ctx.api.conversations.members(conversation_id).get_all()
card = create_conversation_members_card(members)

card_info = CardTaskModuleTaskInfo(
title="Conversation members",
height="small",
width="small",
card=card_attachment(AdaptiveCardAttachment(content=card)),
)

task = TaskModuleContinueResponse(value=card_info)

return MessagingExtensionActionInvokeResponse(task=task)

create_conversation_members_card() method

def create_conversation_members_card(members: List[Account]) -> AdaptiveCard:
"""Create a card showing conversation members."""
members_list = ", ".join(member.name for member in members if member.name)

return AdaptiveCard.model_validate(
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "Conversation members",
"size": "Medium",
"weight": "Bolder",
"color": "Accent",
"style": "heading",
},
{"type": "TextBlock", "text": members_list, "wrap": True, "spacing": "Small"},
],
}
)

Resources​