Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ title: Respond to Search Command in Teams
description: Learn how to respond to the search command from a message extension in a Microsoft Teams app. Understand how to respond to the user request.
ms.topic: article
ms.author: anclear
ms.date: 03/06/2025
ms.date: 04/23/2026
ms.localizationpriority: medium
---
# Respond to search command

[!INCLUDE [bot-based-me-note](../../../includes/messaging-extensions/bot-based-me-note.md)]

[!include[v4-to-v3-SDK-pointer](~/includes/v4-to-v3-pointer-me.md)]

After the user submits the search command, your web service receives a `composeExtension/query` invoke message that contains a `value` object with the search parameters. The invoke is triggered by the following conditions:

* As characters are entered into the search box.
Expand All @@ -29,26 +27,47 @@ The request parameters are found in the `value` object in the request, which inc

# [C#/.NET](#tab/dotnet1)

* [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/msgext-link-unfurling/csharp/Bots/LinkUnfurlingBot.cs#L32)
* [SDK reference](/dotnet/api/microsoft.teams.apps?view=msteams-sdk-dotnet-latest&preserve-view=true)
* [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)
{
// Code to handle the query.
}
teams.OnQuery(async (ctx) =>
{
var commandId = ctx.Activity.Value.CommandId;
var parameters = ctx.Activity.Value.Parameters;
var query = parameters?.FirstOrDefault()?.Value?.ToString() ?? "";
// Code to handle the query.
});
```

# [TypeScript/Node.js](#tab/typescript)

[Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/msgext-search/nodejs/bots/teamsMessagingExtensionsSearchBot.js#L16)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/nodejs/bot-message-extensions/index.ts)

```typescript
class TeamsMessagingExtensionsSearch extends TeamsActivityHandler {
async handleTeamsMessagingExtensionQuery(context, query) {
// Code to handle the query.
}
}
const app = new App()

app.on('message.ext.query', async ({ activity }) => {
const commandId = activity.value?.commandId
const params = activity.value?.parameters || []
const query = params[0]?.value || ''
// Code to handle the query.
})
```

# [Python](#tab/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
app = App()

@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 ""
# Code to handle the query.
```

# [JSON](#tab/json)
Expand Down Expand Up @@ -161,16 +180,14 @@ A `message` response is used when your extension needs to display a plain text m
The following code snippet is an example of a `message` response returned by the app:

```csharp

return new MessagingExtensionResponse
return new MsgExt.Response
{
ComposeExtension = new MsgExt.Result
{
ComposeExtension = new MessagingExtensionResult
{
Type = "message",
Text = "Here is the message you want to show!"
}
};

Type = MsgExt.ResultType.Message,
Text = "Here is the message you want to show!"
}
};
```

:::image type="content" source="../../../assets/images/messaging-extension/message-response-type.png" alt-text="Screenshot shows the message response type.":::
Expand Down Expand Up @@ -202,65 +219,93 @@ To send an Adaptive Card or connector card for Microsoft 365 Groups, you must in

# [.NET](#tab/dotnet)

```csharp
protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
var text = query?.Parameters?[0]?.Value as string ?? string.Empty;
* [SDK reference](/dotnet/api/microsoft.teams.apps?view=msteams-sdk-dotnet-latest&preserve-view=true)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/dotnet/bot-message-extensions/Program.cs)

// Searches NuGet for a package.
var obj = JObject.Parse(await (new HttpClient()).GetStringAsync($"https://azuresearch-usnc.nuget.org/query?q=id:{text}&prerelease=true"));
var packages = obj["data"].Select(item => (item["id"].ToString(), item["version"].ToString(), item["description"].ToString()));

// We take every row of the results and wrap them in cards wrapped in 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 => new MessagingExtensionAttachment
{
ContentType = HeroCard.ContentType,
Content = new HeroCard { Title = package.Item1 },
Preview = new HeroCard { Title = package.Item1, Tap = new CardAction { Type = "invoke", Value = package } }.ToAttachment()
})
.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
}
};
}
```csharp
teams.OnQuery(async (ctx) =>
{
var commandId = ctx.Activity.Value.CommandId;
var parameters = ctx.Activity.Value.Parameters;
var query = parameters?.FirstOrDefault()?.Value?.ToString() ?? "";

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
}
};
});
```

# [TypeScript/Node.js](#tab/typescript2)

[Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/msgext-search-quickstart/js/botActivityHandler.js#L35)
* [Sample code reference](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/TeamsSDK/bot-message-extensions/nodejs/bot-message-extensions/index.ts)

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

const attachments = [];
response.data.objects.forEach(obj => {
const heroCard = CardFactory.heroCard(obj.package.name);
const preview = CardFactory.heroCard(obj.package.name);
const attachment = { ...heroCard, preview };
attachments.push(attachment);
});

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: attachments
}
};
}
}
app.on('message.ext.query', async ({ activity }) => {
const commandId = activity.value?.commandId
const params = activity.value?.parameters || []
const query = params[0]?.value || ''
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,
},
}
})
```

# [JSON](#tab/json2)
Expand Down Expand Up @@ -397,75 +442,35 @@ class TeamsMessagingExtensionsSearchBot extends TeamsActivityHandler {

# [Python](#tab/python1)

* [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):
"""
Handles a Messaging Extension query for searching NPM packages.
@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 ""

This method takes a search query from Teams, fetches matching NPM packages using
the NPM registry API, and returns a list of thumbnail cards displaying package
details with action buttons.
"""
search_query = query.parameters[0].value

response = requests.get(
"http://registry.npmjs.com/-/v1/search",
params={"text": search_query, "size": 8}, # Limit results to top 8
)
response.raise_for_status() # Raise an error if the API call fails
data = response.json() # Parse the JSON response from the API

attachments = []
for obj in data["objects"][:8]: # Iterate through the first 5 search results
package = obj.get("package", {})
package_name = package.get("name", "Unknown Package") # Fallback if name is missing
description = package.get("description", "No description available") # Fallback for missing description
homepage = package.get("links", {}).get("homepage", "https://www.npmjs.com") # Default link

thumbnail_card = ThumbnailCard(
title = package_name, # Package name as card title
text = description, # Package description
buttons=[
# Button to view the package on NPM
CardAction(
type="openUrl",
title="View on NPM",
value=f"https://www.npmjs.com/package/{package_name}",
),
# Button to visit the package's homepage
CardAction(
type="openUrl",
title="Homepage",
value=homepage,
),
],
)

preview_card = ThumbnailCard(
title=package_name,
text = description,
)
preview_attachment = CardFactory.thumbnail_card(preview_card)

preview_attachment.content.tap = CardAction(
type="invoke", # Invoke action triggers a bot command
value={"title": package_name, "description": description},
)

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", # Indicates this is a list of results
attachment_layout="list", # Layout style for results
attachments=attachments,
)
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}'"
Comment thread
Pranjal-MSFT marked this conversation as resolved.
)
)

return MessagingExtensionInvokeResponse(
compose_extension=MessagingExtensionResult(
type=MessagingExtensionResultType.RESULT,
attachment_layout=AttachmentLayout.LIST,
attachments=attachments
)
)
```

Expand Down Expand Up @@ -599,10 +604,10 @@ The default query has the same structure as any regular user query, except it ha

## Code sample

| Sample name | Description | .NET | Node.js | Manifest |
|:---------------------|:--------------|:---------|:--------|:--------|
| Teams message extension search | This sample demonstrates how to create a message extension in Teams that allows users searches for NuGet packages and retrieve the results as a card. |[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)
|Teams message extension auth and config | This sample demonstrates how to implement authentication in a message extension for Teams, enabling secure access and user-specific interactions. |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-auth-config/csharp)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-sso-config/nodejs)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-auth-config/csharp/demo-manifest/msgext-search-auth-config.zip)
| Sample name | Description | .NET | Node.js | Python | Manifest |
|:---------------------|:--------------|:---------|:--------|:--------|:--------|
| 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)| NA |
|Teams message extension auth and config | This sample demonstrates how to implement authentication in a message extension for Teams, enabling secure access and user-specific interactions. |[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-auth-config/csharp)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-sso-config/nodejs)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-auth-config/python)|[View](https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/msgext-search-auth-config/csharp/demo-manifest/msgext-search-auth-config.zip)

## Next step

Expand Down