Microsoft Teams Development & Extensibility — Complete Guide
Tabs · Bots · Messaging Extensions · Adaptive Cards · Meeting Apps · Live Share · SSO · Teams Toolkit · Scenarios · Cheat Sheet
Table of Contents
- Core Concepts — Basics
- Tabs & Bots
- Messaging Extensions & Adaptive Cards
- Meeting Extensions & Live Share
- Teams Toolkit, SSO & Deployment
- Scenario-Based Questions
- Cheat Sheet — Quick Reference
1. Core Concepts — Basics
What are the extensibility points in Microsoft Teams?
Microsoft Teams offers multiple extensibility points for embedding custom apps directly into the Teams client.
| Extensibility Point | Description | Use Case |
|---|---|---|
| Tabs | Web pages embedded as tabs in channels, chats, or personal app | Dashboards, project views, reports |
| Bots | Conversational agents in chats, channels, or personal scope | Notifications, workflows, Q&A |
| Messaging extensions | Interact with apps from the compose box or message actions | Search CRM, create tickets, insert cards |
| Meeting extensions | In-meeting panels, stage sharing, pre/post-meeting tabs | Polls, agendas, collaborative whiteboards |
| Adaptive Cards | Rich interactive cards rendered natively in Teams | Approvals, forms, notifications |
| Connectors / Webhooks | Post messages from external systems into channels | CI/CD alerts, monitoring notifications |
| Task modules (Dialogs) | Modal pop-ups embedding web pages or Adaptive Cards | Complex forms, confirmations, media |
| Link unfurling | Rich card preview when a URL is pasted into Teams | Internal app URL previews |
Tip: All capabilities are packaged in a single Teams app with one manifest. One app can include a tab + bot + messaging extension — all deployable together.
What is the Teams App Manifest?
The Teams App Manifest (manifest.json) is a JSON file defining the app's identity, capabilities, permissions, and entry points. Packaged with icons into a .zip file for deployment.
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
"manifestVersion": "1.17",
"version": "1.0.0",
"id": "{{APP_GUID}}",
"name": { "short": "My App", "full": "My Full App Name" },
"description": { "short": "Short desc", "full": "Full description" },
"icons": { "color": "color.png", "outline": "outline.png" },
"accentColor": "#FFFFFF",
"staticTabs": [...],
"configurableTabs": [...],
"bots": [...],
"composeExtensions": [...],
"permissions": ["identity", "messageTeamMembers"],
"validDomains": ["yourapp.azurewebsites.net"],
"webApplicationInfo": {
"id": "{{ENTRA_APP_ID}}",
"resource": "api://yourapp.azurewebsites.net/{{ENTRA_APP_ID}}"
},
"authorization": {
"permissions": {
"resourceSpecific": [
{ "name": "ChannelMessage.Read.Group", "type": "Application" }
]
}
}
}
App package structure:
myapp.zip
├── manifest.json
├── color.png (192×192 px full-colour icon)
└── outline.png (32×32 px transparent outline icon)
What is the Teams JavaScript SDK?
The Microsoft Teams JavaScript SDK (@microsoft/teams-js) enables web content embedded in Teams to interact with the Teams host client.
import { app, authentication, pages } from '@microsoft/teams-js';
// MUST be called first — initialises host communication:
await app.initialize();
// Get Teams context — rich environment information:
const context = await app.getContext();
console.log(context.user.displayName); // signed-in user
console.log(context.team.displayName); // current team name
console.log(context.channel.id); // current channel ID
console.log(context.app.locale); // en-US, de-DE, etc.
console.log(context.app.theme); // default | dark | contrast
console.log(context.meeting.id); // meeting ID (if in meeting)
console.log(context.page.frameContext); // content | sidePanel | meetingStage
// Register theme change handler:
app.registerOnThemeChangeHandler((theme) => {
document.body.className = theme; // 'default' | 'dark' | 'contrast'
});
// Silent SSO token:
const token = await authentication.getAuthToken();
// Exchange on backend via OBO flow for Graph token
// Tab configuration (configurable tabs):
pages.config.setConfig({
contentUrl: 'https://yourapp.com/tab/content?project=123',
entityId: 'project-123',
suggestedDisplayName: 'Project Dashboard'
});
pages.config.setValidityState(true); // enable Save button
2. Tabs & Bots
What are the types of Teams tabs?
1. Static tabs (personal scope):
→ Appear in the personal app left rail
→ Fixed URL — no configuration dialog
→ Each user sees their own personalised view
→ Use for: personal dashboards, task lists, profile views
→ Manifest: "staticTabs" array, scopes: ["personal"]
2. Configurable tabs (channel/group scope):
→ Added to Teams channels or group chats
→ Shows a configuration page when first added
→ Tab creator sets options — which project, report, or view
→ After config: URL fixed for that channel for all members
→ Use for: team dashboards, project views, shared resources
→ Manifest: "configurableTabs" array, scopes: ["team", "groupchat"]
3. Meeting tabs:
→ Tabs within a Teams meeting context
→ Pre-meeting, in-meeting side panel, post-meeting
→ Manifest: context = ["meetingSidePanel", "meetingStage", "meetingDetailsTab"]
// Static tab:
"staticTabs": [{
"entityId": "personalDashboard",
"name": "My Dashboard",
"contentUrl": "https://yourapp.com/tab/personal",
"websiteUrl": "https://yourapp.com",
"scopes": ["personal"]
}]
// Configurable tab:
"configurableTabs": [{
"configurationUrl": "https://yourapp.com/tab/config",
"canUpdateConfiguration": true,
"scopes": ["team", "groupchat"]
}]
What is a Teams Bot and how is it built?
Teams bots are conversational agents built on the Azure Bot Framework that process messages and events from Teams.
Architecture:
User in Teams → Teams service → Bot Framework Service → Your Bot Web App
← Bot sends response ←
Bot registration steps:
1. Create Azure Bot resource (Bot Framework registration)
2. Generate App ID + Secret (or use Managed Identity)
3. Add Teams channel in Azure Bot configuration
4. Set messaging endpoint: https://yourbot.azurewebsites.net/api/messages
5. Register bot in manifest "bots" array
// Node.js — Teams Bot:
const { TeamsActivityHandler } = require('botbuilder');
class MyTeamsBot extends TeamsActivityHandler {
constructor() {
super();
// Handle incoming messages:
this.onMessage(async (context, next) => {
const text = context.activity.text?.trim();
if (text?.includes('hello')) {
await context.sendActivity('Hello! How can I help you today? 👋');
}
await next();
});
// Handle member added to team/chat:
this.onMembersAdded(async (context, next) => {
for (const member of context.activity.membersAdded) {
if (member.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome ${member.name}! 🎉`);
}
}
await next();
});
// Handle Teams-specific events:
this.onTeamRenamed(async (context, next) => {
await context.sendActivity(`Team renamed to: ${context.activity.channelData.team.name}`);
await next();
});
}
}
Bot activity types in Teams:
| Activity | Trigger |
|---|---|
onMessage |
User sends a message to bot |
onMembersAdded |
User joins team/chat |
onTeamRenamed |
Team is renamed |
onChannelCreated |
New channel created |
onMessageReaction |
User reacts to bot message |
onMeetingStart |
Meeting begins |
onMeetingEnd |
Meeting ends |
What are proactive messages?
Proactive messages are sent by a bot without the user first messaging the bot — for notifications and alerts.
// Step 1: Store conversation reference when user first interacts:
this.onMessage(async (context, next) => {
const ref = TurnContext.getConversationReference(context.activity);
await storeConversationRef(context.activity.from.id, ref); // save to DB
await next();
});
// Step 2: Send proactive message later (e.g., from a timer or webhook):
const ref = await loadConversationRef(userId);
await adapter.continueConversationAsync(
process.env.MicrosoftAppId,
ref,
async (proactiveContext) => {
await proactiveContext.sendActivity(
MessageFactory.text('🔔 Your monthly report is ready!')
);
}
);
// Proactive message to a Teams channel:
const params = {
isGroup: true,
channelData: { channel: { id: channelId } },
activity: MessageFactory.text('📢 Daily standup reminder!')
};
await TeamsInfo.sendMessageToTeamsChannel(context, params, appId, adapter);
Warning: You cannot proactively message a user who has never installed or interacted with your bot. Always handle missing conversation references gracefully.
3. Messaging Extensions & Adaptive Cards
What are messaging extensions?
Messaging extensions let users interact with external services from the Teams compose box or message actions menu.
1. Search commands (query type):
User types in compose box → app searches backend → returns list of cards
→ User selects card → inserts into conversation
→ Use for: searching CRM contacts, Jira tickets, KB articles
2. Action commands:
User right-clicks a message → More actions → your action
→ Opens task module dialog → user fills form → bot processes
→ Use for: create bug from message, file expense, log incident
3. Link unfurling:
User pastes a URL matching your domain
→ Teams calls your app → you return rich Adaptive Card preview
→ Use for: internal app URLs, ticket links, document previews
// Search command manifest:
"composeExtensions": [{
"botId": "{{BOT_ID}}",
"commands": [{
"id": "searchTickets",
"type": "query",
"title": "Search Tickets",
"description": "Search Jira tickets",
"parameters": [{
"name": "searchQuery",
"title": "Search term",
"description": "Ticket number or keyword"
}]
}]
}]
// Search command handler:
async handleTeamsMessagingExtensionQuery(context, query) {
const searchTerm = query.parameters[0].value;
const tickets = await searchJira(searchTerm);
return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: tickets.map(ticket => ({
contentType: 'application/vnd.microsoft.card.adaptive',
content: buildAdaptiveCard(ticket),
preview: {
contentType: 'application/vnd.microsoft.card.thumbnail',
content: { title: ticket.summary, text: ticket.key }
}
}))
}
};
}
What are Adaptive Cards?
Adaptive Cards are platform-agnostic JSON-defined UI components rendered natively in Teams (and Outlook, Windows).
{
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "🎫 Support Ticket #4521",
"weight": "Bolder",
"size": "Medium"
},
{
"type": "FactSet",
"facts": [
{ "title": "Priority", "value": "High" },
{ "title": "Assigned To", "value": "Alice Johnson" },
{ "title": "Status", "value": "Open" }
]
},
{
"type": "Input.Text",
"id": "comment",
"placeholder": "Add a comment...",
"isMultiline": true
}
],
"actions": [
{
"type": "Action.Execute",
"title": "✅ Approve",
"verb": "approve",
"data": { "ticketId": "4521" }
},
{
"type": "Action.Execute",
"title": "❌ Reject",
"style": "destructive",
"verb": "reject",
"data": { "ticketId": "4521" }
},
{
"type": "Action.OpenUrl",
"title": "View in Portal",
"url": "https://helpdesk.contoso.com/ticket/4521"
}
]
}
Adaptive Card elements:
| Category | Elements |
|---|---|
| Text | TextBlock, RichTextBlock |
| Inputs | Input.Text, Input.Number, Input.Date, Input.Time, Input.Toggle, Input.ChoiceSet |
| Layout | Container, ColumnSet, Column, ActionSet, Table |
| Media | Image, Media |
| Actions | Action.Submit, Action.Execute, Action.OpenUrl, Action.ShowCard |
Tip:
Action.Execute(Universal Actions) updates the card in place after user interaction — bot handles the verb and returns an updated card replacing the original. Eliminates the "stale card" problem.
What are Task Modules (Dialogs)?
Task Modules are modal pop-up windows in Teams embedding either a web page or Adaptive Card.
// Bot opens a task module when card action is clicked:
async handleTeamsTaskModuleFetch(context, taskModuleRequest) {
const taskInfo = taskModuleRequest.data;
return {
task: {
type: 'continue',
value: {
title: 'Create Support Ticket',
height: 600,
width: 800,
url: `https://yourapp.com/create-ticket?msgId=${taskInfo.messageId}`
// OR: card: { type: 'AdaptiveCard', ... }
}
}
};
}
// Handle task module submission:
async handleTeamsTaskModuleSubmit(context, taskModuleRequest) {
const { title, priority, description } = taskModuleRequest.data;
const ticket = await createTicket({ title, priority, description });
return {
task: {
type: 'message',
value: `✅ Ticket #${ticket.id} created successfully!`
}
};
}
Use cases:
- Multi-step approval workflows with rich forms
- Complex data entry that doesn't fit in a card
- Confirming destructive actions before execution
- Rich media display (video player, image gallery)
4. Meeting Extensions & Live Share
What are Teams meeting extensibility features?
1. In-meeting side panel:
→ Tab appearing on right side of active meeting
→ Users interact without leaving the meeting view
→ Use for: live polls, Q&A, collaborative notes, agendas
2. Meeting stage (Share to Stage):
→ App content shared to the full meeting screen
→ All participants see the same view simultaneously
→ Use for: whiteboards, shared documents, presentations
3. Pre-meeting tab:
→ Tab in meeting details before meeting starts
→ Use for: agenda setting, pre-meeting polls, document prep
4. Post-meeting tab:
→ Tab accessible after meeting ends
→ Use for: meeting summaries, action items, follow-up
5. Meeting bot:
→ Bot participating in meeting context
→ Can receive meeting events, transcribe, send to meeting chat
// Meeting tab manifest:
"configurableTabs": [{
"configurationUrl": "https://yourapp.com/config",
"canUpdateConfiguration": true,
"scopes": ["groupchat"],
"context": [
"meetingChatTab",
"meetingSidePanel",
"meetingStage",
"meetingDetailsTab"
]
}]
// Get meeting context in tab:
const context = await app.getContext();
const meetingId = context.meeting?.id;
const frameContext = context.page.frameContext;
// frameContext values: 'content' | 'sidePanel' | 'meetingStage'
// Share app to meeting stage (from side panel):
import { meeting } from '@microsoft/teams-js';
await meeting.shareAppContentToStage(
(err) => { if (err) console.error(err); },
'https://yourapp.com/stage-view'
);
What is the Live Share SDK?
Live Share enables real-time synchronised collaborative experiences during Teams meetings — no backend infrastructure needed for synchronisation.
import { LiveShareClient, LiveState, LivePresence } from '@microsoft/live-share';
import { LiveShareHost } from '@microsoft/teams-js';
// Connect to the meeting's Live Share session:
const host = LiveShareHost.create();
const client = new LiveShareClient(host);
const schema = {
initialObjects: {
currentSlide: LiveState,
presence: LivePresence,
}
};
const { container } = await client.joinContainer(schema);
const { currentSlide, presence } = container.initialObjects;
// Initialise shared state:
await currentSlide.initialize(0);
// Listen for remote state changes:
currentSlide.on('stateChanged', (state, local) => {
if (!local) {
updateSlideView(state.data); // another participant changed slide
}
});
// Change state (broadcasts to all participants):
await currentSlide.set(newSlideIndex);
// Track participant presence:
await presence.initialize({ name: 'Alice', cursor: { x: 0, y: 0 } });
presence.on('presenceChanged', (userPresence, local) => {
updateCursor(userPresence); // show all participants' cursors
});
Live Share use cases:
| Scenario | SDK Feature |
|---|---|
| Synchronised video playback | LiveState (position + playing state) |
| Collaborative whiteboard | LivePresence (cursors) + Fluid SharedMap (strokes) |
| Live quiz/polling | LiveState (current question + answers) |
| Shared slide presentation | LiveState (current slide index) |
| Real-time code review | LivePresence (highlights) + Fluid SharedString |
Tip: Live Share uses Azure Fluid Relay under the hood — but developers don't provision any Azure infrastructure. The Teams meeting session automatically provides the relay service.
5. Teams Toolkit, SSO & Deployment
What is Teams Toolkit?
Teams Toolkit is a VS Code extension (and CLI) simplifying building, debugging, and deploying Teams apps.
Key capabilities:
- Project scaffolding: templates for tab (React/Blazor), bot (Node.js/C#), messaging extension, notification bot, workflow bot, SPFx tab, AI chat bot.
- Local development: auto-starts dev tunnel to expose local server — no manual ngrok setup.
- Manifest management: variable substitution (
{{TEAMS_APP_ID}},{{BOT_ID}}) across environments (local, dev, prod). - Provisioning: one-command Azure resource creation — App Service, Bot, Entra app registration all wired together.
- Deployment: deploy to Azure App Service or Azure Functions from VS Code.
- Teams Developer Portal integration: publish to org catalogue or generate sideload package.
# teamsapp.yml (Teams Toolkit v5 pipeline):
provision:
- uses: aadApp/create # create Entra app registration
- uses: botFramework/create # create Azure Bot
- uses: azureAppService/zipDeploy # provision App Service
- uses: teamsApp/create # create Teams app in developer portal
deploy:
- uses: cli/runNpmCommand
with: { args: "run build" }
- uses: azureAppService/zipDeploy
publish:
- uses: teamsApp/publish # publish to org catalogue
How does SSO work in a Teams Tab?
Teams SSO lets tabs silently obtain a token for the signed-in Teams user — no login prompt.
SSO flow:
1. Tab calls: authentication.getAuthToken()
2. Teams host retrieves ID token from Entra ID silently
(audience = your Entra app ID, NOT Microsoft Graph)
3. Tab sends this token to its backend server
4. Backend exchanges via On-Behalf-Of (OBO) flow:
POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
{
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: '{{ENTRA_APP_ID}}',
client_secret: '{{CLIENT_SECRET}}',
assertion: '{teams_sso_token}', // token from step 3
scope: 'https://graph.microsoft.com/User.Read',
requested_token_use: 'on_behalf_of'
}
5. Entra ID returns a Graph access token
6. Backend calls Microsoft Graph with the Graph token
7. Backend returns data to the tab
SSO prerequisites:
// 1. Entra app registration: expose an API
// Application ID URI: api://{hostname}/{entraAppId}
// Scope: access_as_user
// Authorised clients: Teams desktop (5e3ce6c0-...), Teams web (1fec8e78-...)
// 2. manifest.json webApplicationInfo:
"webApplicationInfo": {
"id": "{{ENTRA_APP_ID}}",
"resource": "api://yourapp.azurewebsites.net/{{ENTRA_APP_ID}}"
}
// 3. Tab code:
const token = await authentication.getAuthToken();
const graphData = await fetch('/api/userProfile', {
headers: { Authorization: `Bearer ${token}` }
});
Critical: The SSO token from Teams is NOT a Graph token. It is an ID token for your app — it must be exchanged via the OBO flow on the backend to get a real Graph token. This two-step exchange is the most tested SSO concept in.
What is Resource-Specific Consent (RSC)?
RSC allows Teams apps to request permissions scoped to a specific team or chat — without tenant-wide admin consent.
RSC vs tenant-wide permissions:
Tenant-wide (Graph application permission):
User.Read.All → read ALL users in tenant
Requires: Global Admin consent
Scope: entire tenant
RSC permission:
TeamMember.Read.Group → read members of THIS team only
Requires: team owner consent during app install
Scope: only the specific team where app is installed
Team RSC permissions:
TeamSettings.Read.Group → read team settings
TeamMember.Read.Group → read team members
ChannelMessage.Read.Group → read channel messages
ChannelMessage.Send.Group → send to channels
File.Read.Group → read team's SharePoint files
Chat RSC permissions:
ChatSettings.Read.Chat → read chat settings
ChatMember.Read.Chat → read chat members
ChatMessage.Send.Chat → send to this chat
// manifest.json:
"authorization": {
"permissions": {
"resourceSpecific": [
{ "name": "ChannelMessage.Read.Group", "type": "Application" },
{ "name": "TeamMember.Read.Group", "type": "Application" }
]
}
}
How do you deploy and govern Teams apps in an enterprise?
1. Build package: manifest.json + icons → .zip via Teams Toolkit
2. Publish to org catalogue:
Teams Admin Centre → Teams apps → Manage apps → Upload an app
3. App permission policies:
Control which apps users can install:
→ Microsoft apps | Third-party apps | Custom org apps
Create policy restricting users to approved apps only
4. App setup policies:
→ Pin apps to Teams left rail for all users (or specific groups)
→ Auto-install apps without user action
→ Use for: company-wide tools, mandatory apps
5. Admin consent for permissions:
→ Graph permissions: admin grants consent in Teams admin centre
→ RSC permissions: team owners consent during install
6. CI/CD pipeline using Teams Toolkit CLI:
npm install @microsoft/teamsfx-cli
teamsfx provision --env production
teamsfx deploy --env production
teamsfx publish --env production
7. Teams Developer Portal:
developer.microsoft.com/en-us/microsoft-365/teams
→ Manage apps, validate manifests, analytics
6. Scenario-Based Questions
Scenario: Build a Teams app to raise support tickets from a message.
-
Messaging extension — action command: add "Create Ticket" to message actions menu (right-click → More actions → Create Ticket).
-
Task module: clicking "Create Ticket" opens a modal with a form — title pre-filled from message text, priority dropdown, description field.
-
Bot handles submission:
handleTeamsTaskModuleSubmitreceives form data → calls helpdesk API → creates ticket. -
Adaptive Card confirmation: bot sends card to conversation confirming creation with ticket number, assignee, priority, and portal link.
-
Proactive updates: when ticket status changes (resolved, escalated) → use stored conversation reference to proactively notify the user.
-
Universal Actions (Action.Execute): card includes "Add Comment" and "Escalate" — bot handles verb and returns updated card in place without creating a new message.
Tip: This combines four Teams features: messaging extension + task module + bot + Adaptive Card Universal Actions. Knowing how these connect is the clearest signal of real Teams development experience.
Scenario: Deploy and govern Teams apps in an enterprise.
- Packaging: Teams Toolkit manages per-environment manifests with variable substitution.
- Org catalogue: upload to Teams Admin Centre → available to all users without sideloading.
- Permission policies: restrict users to org-approved apps only; block consumer/third-party apps on specific groups.
- Setup policies: pin the app to the left rail for all users, auto-install in targeted departments via M365 group-based assignment.
- Consent: Graph permissions → admin consent in Teams Admin Centre. RSC permissions → team owners consent on install.
- CI/CD: Azure DevOps pipeline using
teamsfxCLI — provision → deploy → publish as a single automated pipeline. - Monitoring: Teams Developer Portal analytics — active users, install counts, errors. Application Insights for backend telemetry.
Scenario: Build a notification bot sending daily standup reminders.
-
Bot type: notification bot (Teams Toolkit template). Azure Functions timer trigger — cron:
0 0 9 * * 1-5(9am Mon–Fri). -
Installation: when bot is installed in a team → store conversation reference (teamId + channelId + serviceUrl) in Azure Table Storage.
-
Adaptive Card reminder with Input.Text fields for yesterday/today/blockers + Action.Submit button.
-
Response aggregation: when user submits → bot collects all submissions. After timeout or full response → posts summary card to channel.
-
Deployment: Azure Function App (Consumption plan) + Azure Bot Service + Teams app package deployed via Teams Toolkit CI/CD.
Scenario: Teams Tab shows a blank screen in Teams but works in the browser. How do you debug?
-
X-Frame-Options header (most common cause): check if your server returns
X-Frame-Options: DENYorSAMEORIGIN. Teams loads tabs in iframes — these headers block embedding. Replace with CSP:Content-Security-Policy: frame-ancestors teams.microsoft.com *.teams.microsoft.com *.skype.com -
HTTPS required: Teams only loads HTTPS URLs. Local HTTP = blank screen. Use Teams Toolkit's dev tunnel or ngrok.
-
Missing
app.initialize(): must be called before any Teams JS SDK calls. Without it, SDK cannot communicate with the Teams host. -
Domain not in
validDomains: ALL domains loaded by your tab (CDN, API, fonts) must be invalidDomainsin the manifest. Unlisted domains are silently blocked. -
Cookie restrictions: Teams desktop webview blocks third-party cookies. Use
localStorageinstead of session cookies. Use MSAL token caching in memory. -
Debug in Teams client: right-click the tab → Inspect Element → DevTools. Check Console for JS errors and Network tab for blocked requests.
Tip: Always check the X-Frame-Options response header first — it's the single most common cause of blank Teams tabs. Check in browser Network tab before anything else.
7. Cheat Sheet — Quick Reference
Teams Extensibility Quick Map
Need to... → Use
Embed a web page in Teams channel/chat → Configurable Tab
Build a personal app experience → Static Tab
Respond to messages conversationally → Bot
Send notifications to users/channels → Bot + Proactive Messages
Let users search external data in compose box → Messaging Extension (Search)
Create records from Teams messages → Messaging Extension (Action) + Task Module
Show rich interactive cards → Adaptive Cards
Real-time collaboration during meetings → Live Share SDK
Share app to full meeting screen → Meeting Stage
In-meeting sidebar experience → In-meeting Side Panel Tab
Post messages from external systems → Incoming Webhooks
Show rich preview when URL is pasted → Link Unfurling
Authenticate with user's identity silently → Teams SSO + OBO flow
Access specific team data without admin consent → RSC permissions
Manifest Sections Quick Reference
{
"staticTabs": [], // personal scope tabs
"configurableTabs": [], // channel/chat/meeting tabs
"bots": [], // bot configuration
"composeExtensions": [], // messaging extensions
"permissions": [], // identity, messageTeamMembers
"validDomains": [], // ALL domains your app loads
"webApplicationInfo": {}, // Entra app ID for SSO
"authorization": { // RSC permissions
"permissions": {
"resourceSpecific": []
}
}
}
Adaptive Card Actions
Action.Submit: sends form data to bot (classic — creates new activity)
Action.Execute: Universal Actions — server updates card in-place (preferred)
Action.OpenUrl: opens URL in browser
Action.ShowCard: reveals a nested card inline
Universal Action flow (Action.Execute):
User clicks → Teams sends invoke to bot
Bot handles verb → returns updated AdaptiveCard body
Teams replaces the card in place — no new message created
SSO Flow Summary
Tab → authentication.getAuthToken() → Teams ID token (audience = app)
↓
Backend receives Teams token → OBO flow to Entra ID
POST /oauth2/v2.0/token
grant_type: jwt-bearer
assertion: {teams_id_token}
scope: https://graph.microsoft.com/User.Read
↓
Backend receives Graph access token → call Microsoft Graph
↓
Return Graph data to tab
Key: Teams SSO token ≠ Graph token
Must exchange via OBO flow on the server side
Bot Message Types
sendActivity(text) → plain text message
sendActivity(MessageFactory.attachment(card)) → Adaptive Card
sendActivity(MessageFactory.carousel([...])) → carousel of cards
sendActivity(MessageFactory.list([...])) → list of cards
sendActivity(MessageFactory.suggestedActions(text, ['Yes','No'])) → quick replies
Proactive message flow:
1. Store TurnContext.getConversationReference() when user first messages
2. Later: adapter.continueConversationAsync(appId, ref, handler)
3. In handler: context.sendActivity(...)
RSC vs Tenant-Wide Permissions
RSC (Resource-Specific Consent):
→ Consent: team owner (during app install)
→ Scope: only that specific team/chat
→ No Global Admin needed
→ Examples: TeamMember.Read.Group, ChannelMessage.Read.Group
Tenant-wide (Graph application permissions):
→ Consent: Global Admin only
→ Scope: entire tenant
→ Examples: User.Read.All, Mail.Read (ALL mailboxes)
→ High risk — use RSC instead where possible
Top 10 Tips
-
Know all 7 extensibility points — tabs, bots, messaging extensions, meeting apps, Adaptive Cards, webhooks, task modules. Be able to explain each and name a use case for each.
-
Teams SSO token ≠ Graph token — the SSO token from
authentication.getAuthToken()can only be used as an OBO assertion. It must be exchanged on the backend for a real Graph token. This two-step exchange is the most tested SSO concept. -
X-Frame-Options: DENY = blank tab — the single most common cause of blank Teams tabs. Always check this response header first when debugging a tab that works in browser but not in Teams.
-
app.initialize() is mandatory — it must be the first Teams JS SDK call in every tab. Without it, all subsequent SDK calls fail silently.
-
Action.Execute updates cards in place — Universal Actions allow the bot to return an updated card replacing the original, without creating a new message. Far better UX than Action.Submit which creates a new bot reply.
-
Proactive messages require a stored conversation reference — you cannot proactively message a user without a stored reference from a previous interaction. Always capture and store conversation references when users first interact.
-
RSC over tenant-wide permissions where possible — RSC requires only team owner consent and scopes to one team. Tenant-wide application permissions require Global Admin consent and access all tenant data. Least privilege principle.
-
validDomains blocks silently — any domain not in validDomains in the manifest is silently blocked by Teams. Include ALL domains your tab loads — CDN, API endpoints, auth endpoints.
-
Live Share = no backend synchronisation infrastructure — it uses Azure Fluid Relay transparently via the Teams meeting session. For any "real-time meeting collaboration" question, Live Share is the answer.
-
Teams Toolkit manages the full lifecycle — scaffolding, local dev tunnelling, manifest variable substitution, Azure provisioning, deployment, and publishing. Knowing the teamsapp.yml lifecycle pipeline (provision → deploy → publish) shows production maturity.
No comments:
Post a Comment