What Is the Live Share SDK?
The Live Share SDK is Microsoft's framework for building real-time collaborative experiences inside Teams meetings. It is built on top of the Fluid Framework — the same distributed data structure technology that powers Microsoft Loop — and handles all the complexity of synchronising shared state across every meeting participant automatically.
Without Live Share, building any kind of real-time feature in a meeting app requires standing up your own WebSocket server, managing presence lists, handling disconnects, and solving clock skew for timers. Live Share removes all of that. Your app connects to Microsoft's relay infrastructure, and every participant sees the same state within milliseconds — regardless of whether they are on desktop, mobile, or the web client.
The SDK exposes three primary primitives: LivePresence for tracking who is in the session, LiveState for syncing arbitrary application state, and LiveTimer for shared countdowns and elapsed clocks. A fourth primitive, LiveEvent, lets you broadcast ephemeral events (like cursor moves) without persisting them.
Live Share is the right choice when you need state shared across all participants in an active meeting — polling, annotation, whiteboarding, collaborative review, or any "follow the presenter" feature. For persistent data that survives after the meeting ends, combine Live Share with Microsoft Graph or SharePoint.
Installing and Configuring the SDK
The Live Share SDK is distributed as two npm packages. @microsoft/live-share provides the core primitives, and @microsoft/live-share-media adds synchronised media playback for video and audio use cases.
npm install @microsoft/live-share @microsoft/live-share-media npm install @microsoft/teams-js@^2.0.0 fluid-framework
Initialise the Teams JS SDK first, then create a LiveShareClient and call join() to connect to the meeting's shared Fluid container. The container holds all the distributed data structures your app creates.
import { app, meeting } from "@microsoft/teams-js"; import { LiveShareClient, LivePresence, LiveState, LiveTimer } from "@microsoft/live-share"; await app.initialize(); const client = new LiveShareClient(); const schema = { initialObjects: { presence: LivePresence, appState: LiveState<AppState>, timer: LiveTimer, }, }; const { container } = await client.joinContainer(schema); const { presence, appState, timer } = container.initialObjects;
The joinContainer call is idempotent — every participant who calls it connects to the same Fluid container scoped to that meeting. There is no server-side session ID to coordinate; Teams and the Live Share relay handle the scoping automatically.
LivePresence: Showing Who Is in the Session
LivePresence tracks every participant who has joined the Live Share session, along with any custom data you attach to their presence record. Use it to show avatars, highlight the active speaker, or display per-user cursor positions.
interface UserData { displayName: string; role: "presenter" | "attendee"; cursorX: number; cursorY: number; } // Start broadcasting our own presence await presence.initialize({ displayName: currentUser.name, role: "attendee", cursorX: 0, cursorY: 0 }); // Listen for presence updates from all participants presence.on("presenceChanged", (userPresence, local) => { console.log(`${userPresence.data?.displayName} updated — local: ${local}`); setParticipants(presence.getUsers(PresenceState.online)); }); // Update our own cursor position on mouse move canvas.addEventListener("mousemove", (e) => { presence.update({ cursorX: e.offsetX, cursorY: e.offsetY }); });
Presence data is eventually consistent and throttled by the SDK. Do not use it for high-frequency updates like raw pointer events at 60 fps — use LiveEvent for those, or throttle presence updates to 100–200 ms intervals yourself.
LiveState: Synced Application State
LiveState is a single shared state object that any participant can update. When one participant calls set(), every other participant's stateChanged callback fires within milliseconds. It uses last-write-wins semantics, so it is best suited for state where the most recent value is always correct — current slide index, selected poll option, active annotation colour.
interface PollState { question: string; options: string[]; votes: Record<string, string>; // userId → optionIndex phase: "open" | "closed"; } await appState.initialize({ question: "", options: [], votes: {}, phase: "open" }); appState.on("stateChanged", (state, local) => { setPollState(state); }); // Presenter opens a poll function openPoll(question: string, options: string[]) { appState.set({ question, options, votes: {}, phase: "open" }); } // Attendee casts a vote function castVote(userId: string, optionIndex: string) { const current = appState.state!; appState.set({ ...current, votes: { ...current.votes, [userId]: optionIndex } }); }
Live Share throttles state updates to approximately 50 updates per second across the entire container. Calling set() on LiveState more frequently than every 20 ms will result in dropped updates. For high-frequency data such as cursor positions or drawing strokes, use LiveEvent (which is fire-and-forget) or the SharedMap distributed data structure from Fluid Framework directly.
LiveTimer: Shared Countdowns
LiveTimer solves the hard problem of shared time: every participant's clock is slightly different, and network latency means "start the timer" messages arrive at different times. LiveTimer uses the meeting's authoritative server clock so every participant sees the same elapsed time and remaining duration — even participants who join mid-countdown.
await timer.initialize(300000); // 5-minute max duration timer.on("started", ({ duration }) => { console.log(`Timer started — ${duration / 1000}s`); }); timer.on("finished", () => setTimerDone(true)); // Update remaining time every second for display setInterval(() => { if (timer.isRunning) { setRemaining(timer.milliSecondsRemaining); } }, 1000); // Presenter starts the timer function startCountdown(seconds: number) { timer.start(seconds * 1000); } // Presenter pauses function pauseCountdown() { timer.pause(); }
Building a Shared Whiteboard Annotation Tool
A shared annotation tool lets every meeting participant draw on a shared canvas simultaneously — the classic collaborative whiteboard scenario. Combine LivePresence for per-user cursors, a Fluid SharedMap for persistent stroke data, and LiveEvent for low-latency in-progress stroke broadcasting.
import { SharedMap } from "fluid-framework"; import { LivePresence, LiveEvent } from "@microsoft/live-share"; const schema = { initialObjects: { presence: LivePresence, strokeEvent: LiveEvent, // in-progress strokes (ephemeral) strokes: SharedMap, // committed strokes (persistent) }, };
interface StrokePoint { x: number; y: number; } interface StrokeEvent { userId: string; color: string; points: StrokePoint[]; done: boolean; } let currentStroke: StrokePoint[] = []; canvas.addEventListener("pointermove", (e) => { if (!isDrawing) return; currentStroke.push({ x: e.offsetX, y: e.offsetY }); // Broadcast in-progress points via LiveEvent (fire-and-forget) strokeEvent.send({ userId, color: activeColor, points: currentStroke, done: false }); }); canvas.addEventListener("pointerup", () => { // Commit finished stroke to SharedMap (persistent) const strokeId = `${userId}-${Date.now()}`; strokes.set(strokeId, JSON.stringify({ color: activeColor, points: currentStroke })); currentStroke = []; }); // Receive other participants' in-progress strokes strokeEvent.on("received", (event, local) => { if (!local) renderLiveStroke(event.data as StrokeEvent); }); // Replay all committed strokes when joining strokes.forEach((value, key) => { renderCommittedStroke(JSON.parse(value)); });
Integrating into a Teams Meeting Side Panel
Meeting apps surface inside Teams as a side panel — a tab that appears in the meeting stage alongside the video grid. Configuring this requires updating your Teams app manifest to declare the meetingSidePanel context and pointing it at your tab URL.
{
"configurableTabs": [
{
"configurationUrl": "https://yourapp.example.com/config",
"canUpdateConfiguration": true,
"scopes": ["team", "groupchat"],
"context": [
"meetingSidePanel",
"meetingDetailsTab",
"meetingChatTab"
]
}
],
"validDomains": ["yourapp.example.com"]
}The side panel renders your React or plain HTML app in an iframe. The Live Share SDK works inside this iframe — call app.initialize() then joinContainer() as shown earlier. For the meeting stage (full-screen collaborative canvas), add "meetingStage" to the context array and use meeting.shareAppContentToStage() to launch it.
Use meeting.getMeetingDetails() and meeting.getParticipantDetails() to determine whether the current user is an organiser or presenter. Gate state-mutating actions (opening a poll, clearing the whiteboard, starting a timer) behind role checks — do not rely on client-side UI hiding alone.
Deployment and Testing with Multiple Teams Clients
Testing Live Share requires at least two authenticated Teams clients, since the SDK activates only inside an active meeting. The fastest local setup uses ngrok (or Teams Toolkit's built-in tunnelling) to expose your local dev server, then sideloads the app manifest via the Teams Admin Center or the Developer Portal.
# Install Teams Toolkit CLI npm install -g @microsoft/teamsfx-cli # Provision local environment (creates tunnel + AAD app registration) teamsfx provision --env local # Start local dev server with hot reload teamsfx preview --env local
For multi-client testing, open the same meeting in two browser tabs using two different M365 test accounts, or use a second device. Verify that:
- Presence shows both users within 2 seconds of the second client joining.
- LiveState changes made by client A appear on client B within 200 ms under normal network conditions.
- LiveTimer shows the same remaining time (within 1 second tolerance) on both clients.
- A late-joining client (joining 30 seconds after the session started) immediately sees the current state and all committed whiteboard strokes.
For CI/CD, deploy to Azure Static Web Apps or any HTTPS host, update the manifest's validDomains, and submit to your tenant's app catalog. The Teams Admin Center controls which users can install the app without requiring individual sideloading.
Key Takeaways
The Live Share SDK is built on Fluid Framework and handles relay infrastructure — you write distributed data structures, not WebSocket servers.
LivePresence tracks session participants; throttle cursor updates to 100–200 ms or use LiveEvent for high-frequency data.
LiveState uses last-write-wins — ideal for slide index, poll phase, and active tool selection, not for append-only logs.
LiveTimer uses the meeting's authoritative server clock, solving clock skew and late-joiner synchronisation automatically.
Combine LiveEvent (ephemeral) with SharedMap (persistent) for whiteboard tools — events for in-progress strokes, SharedMap for committed ones.
Gate presenter-only actions with server-side role checks via meeting.getParticipantDetails() — never rely on UI hiding alone.
Building a Teams meeting app?
Our Microsoft Teams specialists design and deliver real-time collaborative meeting experiences using the Live Share SDK, SPFx, and Microsoft Graph — from concept to production deployment.