Saturday, May 16, 2026

Microsoft Teams Development & Extensibility Complete Guide

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

  1. Core Concepts — Basics
  2. Tabs & Bots
  3. Messaging Extensions & Adaptive Cards
  4. Meeting Extensions & Live Share
  5. Teams Toolkit, SSO & Deployment
  6. Scenario-Based Questions
  7. 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:

  1. Project scaffolding: templates for tab (React/Blazor), bot (Node.js/C#), messaging extension, notification bot, workflow bot, SPFx tab, AI chat bot.
  2. Local development: auto-starts dev tunnel to expose local server — no manual ngrok setup.
  3. Manifest management: variable substitution ({{TEAMS_APP_ID}}, {{BOT_ID}}) across environments (local, dev, prod).
  4. Provisioning: one-command Azure resource creation — App Service, Bot, Entra app registration all wired together.
  5. Deployment: deploy to Azure App Service or Azure Functions from VS Code.
  6. 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.

  1. Messaging extension — action command: add "Create Ticket" to message actions menu (right-click → More actions → Create Ticket).

  2. Task module: clicking "Create Ticket" opens a modal with a form — title pre-filled from message text, priority dropdown, description field.

  3. Bot handles submission: handleTeamsTaskModuleSubmit receives form data → calls helpdesk API → creates ticket.

  4. Adaptive Card confirmation: bot sends card to conversation confirming creation with ticket number, assignee, priority, and portal link.

  5. Proactive updates: when ticket status changes (resolved, escalated) → use stored conversation reference to proactively notify the user.

  6. 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.

  1. Packaging: Teams Toolkit manages per-environment manifests with variable substitution.
  2. Org catalogue: upload to Teams Admin Centre → available to all users without sideloading.
  3. Permission policies: restrict users to org-approved apps only; block consumer/third-party apps on specific groups.
  4. Setup policies: pin the app to the left rail for all users, auto-install in targeted departments via M365 group-based assignment.
  5. Consent: Graph permissions → admin consent in Teams Admin Centre. RSC permissions → team owners consent on install.
  6. CI/CD: Azure DevOps pipeline using teamsfx CLI — provision → deploy → publish as a single automated pipeline.
  7. Monitoring: Teams Developer Portal analytics — active users, install counts, errors. Application Insights for backend telemetry.

Scenario: Build a notification bot sending daily standup reminders.

  1. Bot type: notification bot (Teams Toolkit template). Azure Functions timer trigger — cron: 0 0 9 * * 1-5 (9am Mon–Fri).

  2. Installation: when bot is installed in a team → store conversation reference (teamId + channelId + serviceUrl) in Azure Table Storage.

  3. Adaptive Card reminder with Input.Text fields for yesterday/today/blockers + Action.Submit button.

  4. Response aggregation: when user submits → bot collects all submissions. After timeout or full response → posts summary card to channel.

  5. 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?

  1. X-Frame-Options header (most common cause): check if your server returns X-Frame-Options: DENY or SAMEORIGIN. 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
    
  2. HTTPS required: Teams only loads HTTPS URLs. Local HTTP = blank screen. Use Teams Toolkit's dev tunnel or ngrok.

  3. Missing app.initialize(): must be called before any Teams JS SDK calls. Without it, SDK cannot communicate with the Teams host.

  4. Domain not in validDomains: ALL domains loaded by your tab (CDN, API, fonts) must be in validDomains in the manifest. Unlisted domains are silently blocked.

  5. Cookie restrictions: Teams desktop webview blocks third-party cookies. Use localStorage instead of session cookies. Use MSAL token caching in memory.

  6. 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

  1. 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.

  2. 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.

  3. 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.

  4. app.initialize() is mandatory — it must be the first Teams JS SDK call in every tab. Without it, all subsequent SDK calls fail silently.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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

Featured Post

Microsoft Teams Development & Extensibility Complete Guide

Microsoft Teams Development & Extensibility — Complete Guide Tabs · Bots · Messaging Extensions · Adaptive Cards · Meeting Apps · Live ...

Popular posts