Skip to main content

🔍 Search commands

Message extension search commands allow users to search external systems and insert the results of that search into a message in the form of a card.

Search command invocation locations

There are two different areas search commands can be invoked from:

  1. Compose Area
  2. Compose Box

Compose Area and Box

Screenshot of Teams with outlines around the 'Compose Box' (for typing messages) and the 'Compose Area' (the menu option next to the compose box that provides a search bar for actions and apps).

Setting up your Teams app manifest

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

"composeExtensions": [
{
"botId": "${{BOT_ID}}",
"commands": [
{
"id": "searchQuery",
"context": [
"compose",
"commandBox"
],
"description": "Test command to run query",
"title": "Search query",
"type": "query",
"parameters": [
{
"name": "searchQuery",
"title": "Search Query",
"description": "Your search query",
"inputType": "text"
}
]
}
]
}
]

Here we are defining the searchQuery search (or query) command.

Handle submission

Handle the search query submission when the searchQuery search command is invoked.

import { cardAttachment } from '@microsoft/teams.api';
import { App } from '@microsoft/teams.apps';
// ...

app.on('message.ext.query', async ({ activity }) => {
const { commandId } = activity.value;
const searchQuery = activity.value.parameters![0].value;

if (commandId == 'searchQuery') {
const cards = await createDummyCards(searchQuery);
const attachments = cards.map(({ card, thumbnail }) => {
return {
...cardAttachment('adaptive', card), // expanded card in the compose box...
preview: cardAttachment('thumbnail', thumbnail), // preview card in the compose box...
};
});

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: attachments,
},
};
}

return { status: 400 };
});

createDummyCards() function

import { ThumbnailCard } from '@microsoft/teams.api';
import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';
// ...

export async function createDummyCards(searchQuery: string) {
const dummyItems = [
{
title: 'Item 1',
description: `This is the first item and this is your search query: ${searchQuery}`,
},
{ title: 'Item 2', description: 'This is the second item' },
{ title: 'Item 3', description: 'This is the third item' },
{ title: 'Item 4', description: 'This is the fourth item' },
{ title: 'Item 5', description: 'This is the fifth item' },
];

const cards = dummyItems.map((item) => {
return {
card: new AdaptiveCard(
new TextBlock(item.title, {
size: 'Large',
weight: 'Bolder',
color: 'Accent',
style: 'heading',
}),
new TextBlock(item.description, {
wrap: true,
spacing: 'Medium',
})
),
thumbnail: {
title: item.title,
text: item.description,
// When a user clicks on a list item in Teams:
// - If the thumbnail has a `tap` property: Teams will trigger the `message.ext.select-item` activity
// - If no `tap` property: Teams will insert the full adaptive card into the compose box
// tap: {
// type: "invoke",
// title: item.title,
// value: {
// "option": index,
// },
// },
} satisfies ThumbnailCard,
};
});

return cards;
}

The search results include both a full adaptive card and a preview card. The preview card appears as a list item in the search command area:

Screenshot of Teams showing a message extensions search menu open with list of search results displayed as preview cards.

When a user clicks on a list item the dummy adaptive card is added to the compose box:

Screenshot of Teams showing the selected adaptive card added to the compose box.

To implement custom actions when a user clicks on a search result item, you can add the tap property to the preview card. This allows you to handle the click event with custom logic:

import { App } from '@microsoft/teams.apps';
// ...

app.on('message.ext.select-item', async ({ activity, send }) => {
const { option } = activity.value;

await send(`Selected item: ${option}`);

return {
status: 200,
};
});

Resources