Skip to content
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
title: Create Search Commands for App
author: vikasalmal
description: Learn about message extension search commands for Teams apps, to create a search command through app manifest and manually.
ms.topic: article
ms.author: anclear
ms.date: 09/16/2024
ms.date: 04/21/2026
ms.localizationpriority: medium
ms.owner: slamba
---
Expand All @@ -16,7 +17,7 @@ The search command is invoked from any one or both of the following locations:
* Compose message area: The buttons at the bottom of the compose message area.
* Command box: By using / in the command box. For example, **/your-app-name**. If you're using the classic Teams, search command is invoked by @mentioning in the command box. For example, **@your-app-name**.

When a search command is invoked from the compose message area, the user sends the results to the conversation. When a search command invoked from the command box, the user interacts with the resulting card, or copies it for use elsewhere.
When a search command is invoked from the compose message area, the user sends the results to the conversation. When a search command is invoked from the command box, the user interacts with the resulting card or copies it for use elsewhere.

The following image displays the invoke locations of the search command:

Expand All @@ -26,31 +27,30 @@ The following image displays the invoke locations of the search command:

To add the search command to your [app manifest](/microsoft-365/extensibility/schema/root-compose-extensions-commands) (previously called Teams app manifest), you must add a new `composeExtensions` object to the top level of your app manifest JSON. You can add the search command either with the help of Developer Portal or manually.

### Create search message extension using Bot Framework
### Create search message extension using Teams SDK

You can create a search message extension using Microsoft 365 Agents Toolkit (previously known as Teams Toolkit) and Developer Portal for Teams.

#### Prerequisites

Before you get started, ensure that you meet the following requirements:

* [Node.js](https://nodejs.org/en). The supported versions are 16, 18.
* [Microsoft 365 account for development](../../../toolkit/tools-prerequisites.md#microsoft-365-developer-program)
* [Set up your dev environment for extending Teams apps across Microsoft 365.](../../../m365-apps/prerequisites.md) After you've enrolled your developer tenant in Office 365 Targeted Release, it might take a couple of days for the enrollment to take effect.
* [Agents Toolkit Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) version 5.2.0 and higher or Microsoft 365 Agents Toolkit CLI (previously known as Teams Toolkit CLI).
* A supported version of [Node.js](https://nodejs.org/en) (16 or 18).
* A [Microsoft 365 account for development](../../../toolkit/tools-prerequisites.md#microsoft-365-developer-program).
* A [dev environment set up for extending Teams apps across Microsoft 365.](../../../m365-apps/prerequisites.md) After you've enrolled your developer tenant in Office 365 Targeted Release, it might take a couple of days for the enrollment to take effect.
* [Agents Toolkit for Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) (version 5.2.0 or later) or Microsoft 365 Agents Toolkit CLI (previously known as Teams Toolkit CLI).

# [Agents Toolkit](#tab/Teams-toolkit)

To create a search-based message extension using Agents Toolkit, follow these steps:

1. Open **Visual Studio Code**.
1. From the left pane, select **Microsoft 365 Agents Toolkit**.
1. Select **Create a New Agents/App** > **Teams App**.
1. Select **Create a New Agents/App** > **Teams Agents and Apps** > **Other Teams Capabilities**.
1. Select **Message Extension**.
1. Select **Custom Search Results**.
1. Select a **programming language**.
1. Select **Default folder**.
1. Enter the name of your app and select **Enter**.
1. Enter the name of your app/agent and select **Enter**.

Agents Toolkit scaffolds your project and creates a search message extension.

Expand Down Expand Up @@ -106,7 +106,7 @@ To run the message extension in Teams, follow these steps:
* Parameter description
* Select the type of input

1. Select **Save**. A search message extension using bot framework created.
1. Select **Save**. A search message extension is created.
1. At the upper-right corner, select **Preview in Teams**. The app opens in Teams desktop or web client.

---
Expand All @@ -124,136 +124,149 @@ The following code provides an example of search-based for message extensions:

# [.NET](#tab/dotnet)

* [SDK reference](/dotnet/api/microsoft.bot.builder.teams.teamsactivityhandler.onteamsmessagingextensionqueryasync?view=botbuilder-dotnet-stable&preserve-view=true)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/app-hello-world/csharp/Microsoft.Teams.Samples.HelloWorld.Web/Bots/MessageExtension.cs#L26-L59)
* [SDK reference](/microsoftteams/platform/teams-sdk/in-depth-guides/message-extensions/search-commands?pivots=csharp)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/dotnet/bot-message-extensions/Program.cs)

```csharp
protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
var text = query?.Parameters?[0]?.Value as string ?? string.Empty;

var packages = new[] {
new { title = "A very extensive set of extension methods", value = "FluentAssertions" },
new { title = "Fluent UI Library", value = "FluentUI" }};

// We take every row of the results and wrap them in cards wrapped in MessagingExtensionAttachment objects.
// The Preview is optional, if it includes a Tap, that will trigger the OnTeamsMessagingExtensionSelectItemAsync event back on this bot.
var attachments = packages.Select(package =>
{
var previewCard = new ThumbnailCard { Title = package.title, Tap = new CardAction { Type = "invoke", Value = package } };
if (!string.IsNullOrEmpty(package.title))
{
previewCard.Images = new List<CardImage>() { new CardImage(package.title, "Icon") };
}

var attachment = new MessagingExtensionAttachment
{
ContentType = HeroCard.ContentType,
Content = new HeroCard { Title = package.title },
Preview = previewCard.ToAttachment()
};

return attachment;
}).ToList();

// The list of MessagingExtensionAttachments must we wrapped in a MessagingExtensionResult wrapped in a MessagingExtensionResponse.
return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = attachments
}
};
}
teams.OnQuery(async (ctx) =>
{
var commandId = ctx.Activity.Value.CommandId;
var parameters = ctx.Activity.Value.Parameters;
var query = parameters?.FirstOrDefault()?.Value?.ToString() ?? "";

Console.WriteLine($"Query: command={commandId}, query={query}");

var attachments = new List<MsgExt.Attachment>();

// Route to appropriate search
if (commandId == "wikipediaSearch")
{
var results = await SearchWikipedia(query);
attachments = results.Select(r =>
{
var title = r["title"]?.ToString() ?? "No Title";
var snippet = Regex.Replace(r["snippet"]?.ToString() ?? "", "<[^>]+>", "");
return CreateAttachment(CreateWikipediaCard(r), title, snippet);
}).ToList();
}

if (attachments.Count == 0)
{
return new MsgExt.Response
{
ComposeExtension = new MsgExt.Result
{
Type = MsgExt.ResultType.Message,
Text = $"No results found for '{query}'"
}
};
}

return new MsgExt.Response
{
ComposeExtension = new MsgExt.Result
{
Type = MsgExt.ResultType.Result,
AttachmentLayout = Attachment.Layout.List,
Attachments = attachments
}
};
});
```

# [Node.js](#tab/nodejs)

[Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/msgext-search-quickstart/js/botActivityHandler.js#L30-L53)

```javascript
async handleTeamsMessagingExtensionQuery(context, query) {
const searchQuery = query.parameters[0].value;
const attachments = [];
const response = await axios.get(`http://registry.npmjs.com/-/v1/search?${ querystring.stringify({ text: searchQuery, size: 8 }) }`);

response.data.objects.forEach(obj => {
const heroCard = CardFactory.heroCard(obj.package.name);
const preview = CardFactory.heroCard(obj.package.name);
preview.content.tap = { type: 'invoke', value: { description: obj.package.description } };
const attachment = { ...heroCard, preview };
attachments.push(attachment);
});

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: attachments
}
};
}
}
# [TypeScript](#tab/typescript)

* [SDK reference](/microsoftteams/platform/teams-sdk/in-depth-guides/message-extensions/search-commands?pivots=typescript)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/nodejs/bot-message-extensions/index.ts)

```TypeScript
app.on('message.ext.query', async ({ activity }) => {
const commandId = activity.value?.commandId
const params = activity.value?.parameters || []
const query = params[0]?.value || ''
console.log(`Query: command=${commandId}, query=${query}`)
let attachments: MessagingExtensionAttachment[] = []

if (commandId === 'wikipediaSearch') {
const results = await searchWikipedia(query)
attachments = results.map((r: any) =>
createAttachment(
createWikipediaCard(r),
r.title,
(r.snippet || '').replace(/<[^>]+>/g, '')
)
)
}

if (!attachments.length) {
return {
composeExtension: {
type: 'message',
text: `No results found for '${query}'`,
},
}
}

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

# [Python](#tab/python)

* [SDK reference](/microsoftteams/platform/teams-sdk/in-depth-guides/message-extensions/search-commands?pivots=python)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/python/bot-message-extensions/main.py)

```python
async def on_teams_messaging_extension_query(self, context, query):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the code snippet

@app.on_message_ext_query
async def handle_query(ctx: ActivityContext[MessageExtensionQueryInvokeActivity]):
command_id = ctx.activity.value.command_id
params = ctx.activity.value.parameters or []
query = params[0].value if params else ""

print(f"Query: command={command_id}, query={query}")

# Route to appropriate search
if command_id == "wikipediaSearch":
    results = await search_wikipedia(query)
    attachments = [
        create_attachment(
            create_wikipedia_card(r),
            r['title'],
            re.sub(r'<[^>]+>', '', r.get('snippet', ''))
        )
        for r in results
    ]

if not attachments:
    return MessagingExtensionResponse(
        compose_extension=MessagingExtensionResult(
            type=MessagingExtensionResultType.MESSAGE,
            text=f"No results found for '{query}'"
        )
    )

return MessagingExtensionResponse(
    compose_extension=MessagingExtensionResult(
        type=MessagingExtensionResultType.RESULT,
        attachment_layout=MessagingExtensionAttachmentLayout.LIST,
        attachments=attachments
    )
)

"""
Handles Messaging Extension queries in Teams.

This method generates a list of thumbnail cards containing random text and images when the "getRandomText" command is triggered. It creates preview cards with tap actions and returns them as a Messaging Extension response.
"""
faker = Faker()
title = query.command_id
random_image_url = "https://loremflickr.com/200/200"

if query.command_id == "getRandomText":
attachments = []
# Generate 5 results with fake text and fake images
for i in range(5):
text = faker.paragraph()
images = [f"{random_image_url}?random={i}"]

# Create a thumbnail card using ThumbnailCard
thumbnail_card = self.create_thumbnail_card(title, text, images)

# Create a preview card and add the tap action
preview_card = self.create_thumbnail_card(title, text, images)
tap_action = CardAction(
type="invoke",
value={"title": title, "text": text, "images": images},
@app.on_message_ext_query
async def handle_query(ctx: ActivityContext[MessageExtensionQueryInvokeActivity]):
command_id = ctx.activity.value.command_id
params = ctx.activity.value.parameters or []
query = params[0].value if params else ""

print(f"Query: command={command_id}, query={query}")

# Route to appropriate search
if command_id == "wikipediaSearch":
results = await search_wikipedia(query)
attachments = [
create_attachment(
create_wikipedia_card(r),
r["title"],
re.sub(r"&lt;[^&gt;]+&gt;", "", r.get("snippet", "")),
)
preview_attachment = CardFactory.thumbnail_card(preview_card)
preview_attachment.content.tap = tap_action

# Combine the thumbnail card and the preview
attachment = MessagingExtensionAttachment(
content = thumbnail_card,
content_type=CardFactory.content_types.thumbnail_card,
preview=preview_attachment,
)
attachments.append(attachment)
for r in results
]

if not attachments:
return MessagingExtensionResponse(
compose_extension=MessagingExtensionResult(
type="result",
attachment_layout="list",
attachments=attachments,
type=MessagingExtensionResultType.MESSAGE,
text=f"No results found for '{query}'",
)
)

return MessagingExtensionResponse(
compose_extension=MessagingExtensionResult(
type=MessagingExtensionResultType.RESULT,
attachment_layout=MessagingExtensionAttachmentLayout.LIST,
attachments=attachments,
)
)
```

---

## Code sample

| Sample name | Description | .NET | Node.js | Manifest|
| Sample name | Description | .NET | Node.js | Python|
|:---------------------|:--------------|:---------|:--------|:--------------|
|Teams message extension search | This sample demonstrates how to create a Messaging Extension in Microsoft Teams that allows users to perform searches and retrieve results. |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search/csharp)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search/nodejs)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search/csharp/demo-manifest/msgext-search.zip)
|Bot Message Extensions | This sample demonstrates a search-based messaging extension in Microsoft Teams that allows users to search for Wikipedia articles. The extension supports search commands, item selection, and link unfurling. |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/dotnet/bot-message-extensions)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/nodejs/bot-message-extensions)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/TeamsSDK/bot-message-extensions/python/bot-message-extensions)

## Next step

Expand Down