100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Microsoft 365

Message Extensions

Message extensions let users search external systems or trigger actions directly from the Teams compose box, message context menu, or link previews.

Building Teams AppsIntermediate9 min readJul 10, 2026
Analogies

Search Commands vs. Action Commands

Message extensions (declared as composeExtensions in the manifest) come in two command types. A search command shows a text box in the compose box; as the user types, Teams calls your bot's messaging endpoint with a composeExtension/query invoke activity, and your handler (handleTeamsMessagingExtensionQuery) returns a list of MessagingExtensionResult attachments the user can click to insert into their message, similar to searching Giphy or Jira issues without leaving the compose box. An action command instead opens a modal task module (built with an Adaptive Card or web page) via fetchTask, collects structured input, and on submit calls handleTeamsMessagingExtensionSubmitAction to process the data and optionally insert a formatted card into the conversation.

🏏

Cricket analogy: A search command is like scanning a live scorecard database mid-over to pull up a specific player's stats to share in commentary, while an action command is like filling out an official post-match report form that generates the summary card everyone sees.

Task Modules and the Submit Flow

The task module lifecycle for an action command starts when the user selects the command from the '...' compose menu; Teams sends a composeExtension/fetchTask invoke, and your bot responds with a TaskModuleResponse pointing either at an Adaptive Card or an iframe-hosted web page. When the user fills in the form and submits, Teams sends composeExtension/submitAction with the collected data, and your handler returns either a botMessagePreview (letting the user review an editable card before it posts) or a result attachment inserted directly into the compose box. This same fetchTask/submitAction pattern also powers link unfurling for message extensions, where posting a recognized domain URL triggers a preview card instead of a raw link.

🏏

Cricket analogy: The fetchTask/submitAction round trip is like a DRS review: the on-field decision (fetchTask) triggers a review process, and the third umpire's final ruling (submitAction) is what actually gets displayed on the big screen.

Registering Commands in the Manifest

Each composeExtensions entry references a botId and lists commands, where each command has an id, a type of query or action, a context array (compose, commandBox, message) controlling where it appears, and parameters describing the search fields shown to the user. A command with context including message appears in the '...' menu on an existing chat message, commonly used for action commands like 'Create ticket from this message' that pre-fill a form with the message's text. Link unfurling instead uses a messageHandlers array with a linkUnfurling type and a domains list, so posting a URL from an allowed domain automatically triggers your fetchTask handler with the URL passed in.

🏏

Cricket analogy: The context array controlling where a command appears is like a fielding restriction rule active only during powerplay overs, the same player is available, but where they can operate is scoped by context.

json
{
  "composeExtensions": [
    {
      "botId": "9c4b1f2e-7a3d-4e5c-8f1a-2b3c4d5e6f7a",
      "commands": [
        {
          "id": "searchTickets",
          "type": "query",
          "context": ["compose", "commandBox"],
          "title": "Search Tickets",
          "parameters": [
            { "name": "searchQuery", "title": "Search", "description": "Ticket ID or keyword" }
          ]
        },
        {
          "id": "createTicketFromMessage",
          "type": "action",
          "context": ["message"],
          "title": "Create Ticket",
          "fetchTask": true
        }
      ],
      "messageHandlers": [
        {
          "type": "link",
          "value": { "domains": ["helpdesk.contoso.com"] }
        }
      ]
    }
  ]
}

handleTeamsMessagingExtensionQuery results support multiple layout types: list (title/subtitle/icon rows) and grid (thumbnail cards), controlled by the resultType field on the response. Grid layout is typically used for image-heavy search results like a Giphy-style extension.

Link unfurling only fires for domains explicitly listed under messageHandlers, and unlike validDomains for tabs, this list is separately declared per compose extension — adding a domain to validDomains alone will not enable unfurling for it.

  • Message extensions have two command types: query (search-as-you-type) and action (modal form via task module).
  • Search commands use handleTeamsMessagingExtensionQuery to return list or grid results the user can insert.
  • Action commands use fetchTask to open a form and handleTeamsMessagingExtensionSubmitAction to process submission.
  • botMessagePreview lets a user review and edit a generated card before it posts to the conversation.
  • context: message scopes a command to appear on the '...' menu of existing chat messages.
  • Link unfurling reuses the fetchTask/submitAction pattern, gated by a separate messageHandlers domains list.
  • Commands are declared per composeExtensions entry in the manifest, referencing the same botId as the bot.

Practice what you learned

Was this page helpful?

Topics covered

#Microsoft365#MicrosoftTeamsDevelopmentStudyNotes#MicrosoftTechnologies#MessageExtensions#Message#Extensions#Search#Commands#StudyNotes#SkillVeris