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/02/2026
ms.localizationpriority: medium
ms.owner: slamba
---
Expand All @@ -26,7 +27,7 @@ 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.

Expand All @@ -45,12 +46,11 @@ To create a search-based message extension using Agents Toolkit, follow these st

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)
Comment thread
Pranjal-MSFT marked this conversation as resolved.
Outdated

[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
}
};
}
}
* [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.
@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 MessagingExtensionInvokeResponse(
compose_extension=MessagingExtensionResult(
type=MessagingExtensionResultType.MESSAGE,
text=f"No results found for '{query}'"
)
)

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},
)
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)

return MessagingExtensionResponse(
compose_extension=MessagingExtensionResult(
type="result",
attachment_layout="list",
attachments=attachments,
)
)
return MessagingExtensionInvokeResponse(
compose_extension=MessagingExtensionResult(
type=MessagingExtensionResultType.RESULT,
attachment_layout=AttachmentLayout.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