-
Notifications
You must be signed in to change notification settings - Fork 192
How to extend Link Unfurling template
Zero Install Link Unfurling requires link unfurling app to be published. You need an admin account to publish an app into your org.
Login your admin account in Teams. Go to Manage your apps
-> Upload an app
. Click Upload an app to your org's app catalog
to upload your app's zip file.
Switch to another user account. Without installing this app, paste the link "https://www.botframework.com" into chat box, and you should see the adaptive card like below.
This template removes cache by default to provide convenience for debug. To add cache, REMOVE following JSON part from adaptive card in linkUnfurlingApp.ts
(linkUnfurlingApp.js
):
suggestedActions: {
actions: [
{
title: "default",
type: "setCachePolicy",
value: '{"type":"no-cache"}',
},
],
}
After removing this, the link unfurling result will be cached in Teams for 30 minutes. Please refer to link unfurling document for more details.
The supported types for Zero Install Link Unfurling are "result" and "auth" and this template uses "result" as default. By changing it to "auth", the adaptive card will be:
For card with type "auth", the Teams client strips away any action buttons from the card, and adds a sign in action button. Please refer to zero install link unfurling document for more details.
Stage View is a full screen UI component that you can invoke to surface your web content. You can turn URLs into a tab using an Adaptive Card and Chat Services.
Collaborative Stage View is an enhancement to Stage View that allows users to engage with your app content in a new Teams window. When a user opens Collaborative Stage View from an Adaptive Card, the app content pops-out in a new Teams window instead of the default Stage View modal.
Follow these steps to add stage view or collaborative stage view to your link unfurling template.
- Step 1: Update manifest.json
- Step 2: Add route for
/tab
- Step 3: Set
BOT_DOMAIN
andTEAMS_APP_ID
in environment variables orappsettings.json
- Step 4: Update adaptive card (stage view only)
- Step 4: Update adaptive card (collaborative stage view only)
In appPackage/manifest.json
, update validDomains
section.
"validDomains": [
"${{BOT_DOMAIN}}"
]
For typescript and javascript template, in src/index.ts
(src/index.js
), add following code.
expressApp.get("/tab", async (req, res) => {
const body = `<!DOCTYPE html>
<html lang="en">
<div class="text-center">
<h1 class="display-4">Tab in stage View</h1>
</div>
</html>`;
res.writeHead(200, {
"Content-Length": Buffer.byteLength(body),
"Content-Type": "text/html",
});
res.write(body);
res.end();
});
For c# template, in Controllers/
, add a new TabController
:
[Route("tab")]
[ApiController]
public class TabController : ControllerBase
{
[HttpPost, HttpGet]
public ContentResult Get()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
var html = "<h1>Tab in stage view</h1>";
return new ContentResult
{
Content = html,
ContentType = "text/html"
};
}
}
For local debug:
If it's typescript or javascript template, update action file/createOrUpdateEnvironmentFile
in teamsapp.local.yml
, add TEAMS_APP_ID
and BOT_DOMAIN
to env.
- uses: file/createOrUpdateEnvironmentFile # Generate runtime environment variables
with:
target: ./.localConfigs
envs:
BOT_ID: ${{BOT_ID}}
BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
TEAMS_APP_ID: ${{TEAMS_APP_ID}}
BOT_DOMAIN: ${{BOT_DOMAIN}}
If it's c# template, update action file/createOrUpdateJsonFile
in teamsapp.local.yml
, add TEAMS_APP_ID
and BOT_DOMAIN
to appsettings.json
.
- uses: file/createOrUpdateJsonFile
with:
target: ./appsettings.Development.json
appsettings:
BOT_ID: ${{BOT_ID}}
BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
TEAMS_APP_ID: ${{TEAMS_APP_ID}}
BOT_DOMAIN: ${{BOT_DOMAIN}}
For remote:
Update infra/azure.parameters.json
. Add following to parameters
:
"teamsAppId":{
"value": "${{TEAMS_APP_ID}}"
}
Add following to infra/azure.bicep
(if it's c# template, remove line WEBSITE_NODE_DEFAULT_VERSION: '~18'
):
param teamsAppId string
resource webAppSettings 'Microsoft.Web/sites/config@2022-09-01' = {
parent: webApp
name: 'appsettings'
properties: {
BOT_DOMAIN: webApp.properties.defaultHostName
BOT_ID: botAadAppClientId
BOT_PASSWORD: botAadAppClientSecret
RUNNING_ON_AZURE: '1'
TEAMS_APP_ID: teamsAppId
WEBSITE_NODE_DEFAULT_VERSION: '~18'
WEBSITE_RUN_FROM_PACKAGE: '1'
}
}
In src/adaptiveCards/helloWorldCard.json
or Resources/helloWorldCard.json
, update actions
to be following.
"actions": [
{
"type": "Action.OpenUrl",
"title": "View Via Deep Link",
"url": "https://teams.microsoft.com/l/stage/${appId}/0?context=%7B%22contentUrl%22%3A%22https%3A%2F%2F${url}%2Ftab%22%2C%22websiteUrl%22%3A%22https%3A%2F%2F${url}%2Ftab%22%2C%22name%22%3A%22DemoStageView%22%7D"
}
],
For typescript or javascript templates:
Run npm install adaptive-expressions adaptivecards-templating
to install the package for adaptivecards templating.
In src/linkUnfurlingApp.ts
(src/linkUnfurlingApp.js
), update variable attachment
to be following.
const template = new ACData.Template(helloWorldCard);
const card = template.expand({
$root: {
url: process.env.BOT_DOMAIN,
appId: process.env.TEAMS_APP_ID,
},
});
const attachment = { ...CardFactory.adaptiveCard(card), preview: previewCard };
For c# templates:
run dotnet add package AdaptiveCards.Templating
to install the package for adaptivecards templating.
Update Config.cs
:
public class ConfigOptions
{
public string BOT_ID { get; set; }
public string BOT_PASSWORD { get; set; }
public string TEAMS_APP_ID { get; set; }
public string BOT_DOMAIN { get; set; }
}
Update LinkUnfurlingApp
class:
private readonly ConfigOptions _config;
public LinkUnfurlingApp(ConfigOptions config)
{
_config = config;
}
Update variable adaptiveCard
to be following:
var data = new { url = _config.BOT_DOMAIN, appId = _config.TEAMS_APP_ID };
var template = new AdaptiveCards.Templating.AdaptiveCardTemplate(adaptiveCardJson);
var adaptiveCard =AdaptiveCard.FromJson(template.Expand(data)).Card;
Update program.cs
:
builder.Services.AddTransient<IBot>(sp => new LinkUnfurlingApp(config));
In Teams, the adaptive card will be like:
Opening stage view from Adaptive card:
In Outlook, the adaptive card will be like:
Opening stage view from Adaptive card via deep link:
Please refer to Stage view document for more details.
In src/adaptiveCards/helloWorldCard.json
or Resources/helloWorldCard.json
, update actions
to be following.
"actions": [
{
"type": "Action.Submit",
"title": "View Via card",
"data":{
"msteams": {
"type": "invoke",
"value": {
"type": "tab/tabInfoAction",
"tabInfo": {
"contentUrl": "https://${url}/tab",
"websiteUrl": "https://${url}/tab",
"entityId": "entityId"
}
}
}
}
}
],
For typescript or javascript templates:
Run npm install @microsoft/adaptivecards-tools
to install the package for adaptivecards templating.
In src/linkUnfurlingApp.ts
(src/linkUnfurlingApp.js
), update variable attachment
to be following.
const data = { url: process.env.BOT_DOMAIN, appId: process.env.TEAMS_APP_ID };
const renderedCard = AdaptiveCards.declare(helloWorldCard).render(data);
const attachment = {
...CardFactory.adaptiveCard(renderedCard),
preview: previewCard,
};
For c# templates:
run dotnet add package AdaptiveCards.Templating
to install the package for adaptivecards templating.
Update Config.cs
:
public class ConfigOptions
{
public string BOT_ID { get; set; }
public string BOT_PASSWORD { get; set; }
public string TEAMS_APP_ID { get; set; }
public string BOT_DOMAIN { get; set; }
}
Update LinkUnfurlingApp
class:
private readonly ConfigOptions _config;
public LinkUnfurlingApp(ConfigOptions config)
{
_config = config;
}
Update variable adaptiveCard
to be following:
var data = new { url = _config.BOT_DOMAIN, appId = _config.TEAMS_APP_ID };
var template = new AdaptiveCards.Templating.AdaptiveCardTemplate(adaptiveCardJson);
var adaptiveCard =AdaptiveCard.FromJson(template.Expand(data)).Card;
Update program.cs
:
builder.Services.AddTransient<IBot>(sp => new LinkUnfurlingApp(config));
In Teams client, the adaptive card will be like:
Opening collaborative stage view from Adaptive card:
Please refer to Collaborative Stage View for more details.
Once your link is unfurled into an Adaptive Card and sent in conversation, you can use Task modules to create modal pop-up experiences in your Teams application. Follow the instructions below to add task module in your link unfurling app.
- Step 1: Update adaptive card
- Step 2: Add
handleTeamsTaskModuleFetch
function in handler - Step 3: Add
handleTeamsTaskModuleSubmit
function in handler
In src/adaptiveCards/helloWorldCard.json
(Resources/helloWorldCard.json
for c# template), update actions
to be following.
"actions": [
{
"type": "Action.Submit",
"title": "Task module",
"data": {
"msteams": {
"type": "task/fetch",
"data": "task module"
}
}
}
],
For typescript template, in src/linkUnfurlingApp.ts
, add following method to LinkUnfurlingApp
class.
public async handleTeamsTaskModuleFetch(context: TurnContext, taskModuleRequest: TaskModuleRequest): Promise<TaskModuleResponse> {
return {
task: {
type: "continue",
value: {
title: "Task Module Fetch",
height: 200,
width: 400,
card: CardFactory.adaptiveCard({
version: '1.0.0',
type: 'AdaptiveCard',
body: [
{
type: 'TextBlock',
text: 'Enter Text Here'
},
{
type: 'Input.Text',
id: 'usertext',
placeholder: 'add some text and submit',
IsMultiline: true
}
],
actions: [
{
type: 'Action.Submit',
title: 'Submit'
}
]
})
},
},
};
}
For javascript template, in src/linkUnfurlingApp.js
, add following method to LinkUnfurlingApp
class.
handleTeamsTaskModuleFetch(context, taskModuleRequest) {
return {
task: {
type: "continue",
value: {
title: "Task Module Fetch",
height: 200,
width: 400,
card: CardFactory.adaptiveCard({
version: '1.0.0',
type: 'AdaptiveCard',
body: [
{
type: 'TextBlock',
text: 'Enter Text Here'
},
{
type: 'Input.Text',
id: 'usertext',
placeholder: 'add some text and submit',
IsMultiline: true
}
],
actions: [
{
type: 'Action.Submit',
title: 'Submit'
}
]
})
},
},
};
}
For c# template, in LinkUnfurling/LinkUnfurlingApp.cs
, add following method to LinkUnfurlingApp
class.
protected override Task<TaskModuleResponse> OnTeamsTaskModuleFetchAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
{
return Task.FromResult(new TaskModuleResponse
{
Task = new TaskModuleContinueResponse
{
Type = "continue",
Value = new TaskModuleTaskInfo
{
Title = "Task Module Fetch",
Height = 200,
Width = 400,
Card = new Attachment
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = new AdaptiveCard("1.0.0")
{
Version = "1.0.0",
Type = "AdaptiveCard",
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock
{
Text = "Enter Text Here",
Type = "TextBlock"
},
new AdaptiveTextInput
{
Id = "usertext",
Placeholder = "add some text and submit",
IsMultiline = true,
Type = "Input.Text"
}
},
Actions = new List<AdaptiveAction>
{
new AdaptiveSubmitAction
{
Title = "Submit",
Type = "Action.Submit"
}
}
}
}
}
}
});
}
For typescript template, in src/linkUnfurlingApp.ts
, add following method to LinkUnfurlingApp
class.
public async handleTeamsTaskModuleSubmit(context: TurnContext, taskModuleRequest: TaskModuleRequest): Promise<TaskModuleResponse> {
return {
task: {
type: 'message',
value: 'Thanks!'
}
};
}
For javascript template, in src/linkUnfurlingApp.js
, add following method to LinkUnfurlingApp
class.
handleTeamsTaskModuleSubmit(context, taskModuleRequest) {
return {
task: {
type: 'message',
value: 'Thanks!'
}
};
}
For c# template, in LinkUnfurling/LinkUnfurlingApp.cs
, add following method to LinkUnfurlingApp
class.
protected override Task<TaskModuleResponse> OnTeamsTaskModuleSubmitAsync(ITurnContext<IInvokeActivity> turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
{
return Task.FromResult(new TaskModuleResponse
{
Task = new TaskModuleMessageResponse
{
Type = "message",
Value = "Thanks!"
}
});
}
In Teams, the adaptive card will be like:
Click "Task module" button:
Click "Submit" button:
Please refer to Task module document for more details.
Adaptive Card actions allow users to interact with your card by clicking a button or selecting a choice. Follow the instructions below to add adaptive card action in your link unfurling app.
- Step 1: Update
bots
section in manifest - Step 2: Update adaptive card
- Step 3: Add
onAdaptiveCardInvoke
function in handler
The card action requires bot capability. In appPackage/manifest.json
, update bots
section to be following.
"bots": [
{
"botId": "${{BOT_ID}}",
"scopes": [
"team",
"personal",
"groupchat"
],
"supportsFiles": false,
"isNotificationOnly": false
}
]
In src/adaptiveCards/helloWorldCard.json
(Resources/helloWorldCard.json
for c# template), update actions
to be following.
"actions": [
{
"type": "Action.Execute",
"title": "card action",
"verb": "cardAction",
"id": "cardAction"
}
],
For typescript template, in src/linkUnfurlingApp.ts
, add following method to LinkUnfurlingApp
class.
public async onAdaptiveCardInvoke(context: TurnContext, invokeValue: AdaptiveCardInvokeValue): Promise<AdaptiveCardInvokeResponse> {
const card = {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "Your response was sent to the app",
"size": "Medium",
"weight": "Bolder",
"wrap": true
},
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4"
};
const res = { statusCode: 200, type: "application/vnd.microsoft.card.adaptive", value: card };
return res;
}
For javascript template, in src/linkUnfurlingApp.js
, add following method to LinkUnfurlingApp
class.
onAdaptiveCardInvoke(context, invokeValue) {
const card = {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "Your response was sent to the app",
"size": "Medium",
"weight": "Bolder",
"wrap": true
},
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4"
};
const res = { statusCode: 200, type: "application/vnd.microsoft.card.adaptive", value: card };
return res;
}
For c# template, in LinkUnfurling/LinkUnfurlingApp.cs
, add following method to LinkUnfurlingApp
class.
protected override Task<AdaptiveCardInvokeResponse> OnAdaptiveCardInvokeAsync(ITurnContext<IInvokeActivity> turnContext, AdaptiveCardInvokeValue invokeValue, CancellationToken cancellationToken)
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion("1.4"))
{
Body = new List<AdaptiveElement>
{
new AdaptiveTextBlock
{
Text = "Your response was sent to the app",
Size = AdaptiveTextSize.Medium,
Weight = AdaptiveTextWeight.Bolder,
Wrap = true,
Type = "TextBlock"
}
}
};
var response = new AdaptiveCardInvokeResponse
{
StatusCode = 200,
Type = "application/vnd.microsoft.card.adaptive",
Value = card,
};
return Task.FromResult(response);
}
In Teams, the adaptive card will be like:
Click card action
button, the adaptive card will be updated to be following:
Please refer to Universal actions document for more details.
The Notification, Command and Workflow Bot are scenario templates provided by Teams Toolkit. These templates have similar structure. This guide takes Notification Bot as an example.
Select the Teams Toolkit icon on the left in the VS Code toolbar. Choose "Create a New App"->"Bot"->"Chat Notification Message". Wait for the download complete.
Copy all methods from src/linkUnfurlingApp.ts
class to Notification Bot's empty TeamsActivityHandler
in src/teamsBot.ts
.
Copy composeExtension
section in your appPackage/manifest.json
to Notification bot's appPackage/manifest.json
.
Now your Notification bot project has both notification and link unfurling function.
Build Custom Engine Copilots
- Build a basic AI chatbot for Teams
- Build an AI agent chatbot for Teams
- Expand AI bot's knowledge with your content
Scenario-based Tutorials
- Send notifications to Teams
- Respond to chat commands in Teams
- Respond to card actions in Teams
- Embed a dashboard canvas in Teams
Extend your app across Microsoft 365
- Teams tabs in Microsoft 365 and Outlook
- Teams message extension for Outlook
- Add Outlook Add-in to a Teams app
App settings and Microsoft Entra Apps
- Manage Application settings with Teams Toolkit
- Manage Microsoft Entra Application Registration with Teams Toolkit
- Use an existing Microsoft Entra app
- Use a multi-tenant Microsoft Entra app
Configure multiple capabilities
- How to configure Tab capability within your Teams app
- How to configure Bot capability within your Teams app
- How to configure Message Extension capability within your Teams app
Add Authentication to your app
- How to add single sign on in Teams Toolkit for Visual Studio Code
- How to enable Single Sign-on in Teams Toolkit for Visual Studio
Connect to cloud resources
- How to integrate Azure Functions with your Teams app
- How to integrate Azure API Management
- Integrate with Azure SQL Database
- Integrate with Azure Key Vault
Deploy apps to production