| name | zoom-meeting-sdk-web |
| description | Zoom Meeting SDK for Web - Embed Zoom meeting capabilities into web applications. Two integration
options: Client View (full-page, familiar Zoom UI) and Component View (embeddable, Promise-based API).
Includes SharedArrayBuffer setup for HD video, gallery view, and virtual backgrounds.
|
| user-invocable | false |
| triggers | ["embed meeting web","meeting in react","meeting in nextjs","meeting in vue","meeting in angular","component view","client view","web meeting sdk","javascript meeting","sharedarraybuffer"] |
Zoom Meeting SDK (Web)
Embed Zoom meeting capabilities into web applications with two integration options: Client View (full-page) or Component View (embeddable).
How to Implement a Custom Video User Interface for a Zoom Meeting in a Web App
Use Meeting SDK Web Component View.
Do not use Video SDK for this question unless the user is explicitly building a non-meeting session
product.
Minimal architecture:
Browser page
-> fetch Meeting SDK signature from backend
-> ZoomMtgEmbedded.createClient()
-> client.init({ zoomAppRoot })
-> client.join({ signature, sdkKey, meetingNumber, userName, password })
-> apply layout/style/customize options around the embedded meeting container
Minimal implementation:
import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';
const client = ZoomMtgEmbedded.createClient();
export async function startEmbeddedMeeting(meetingNumber: string, userName: string, password: string) {
const sigRes = await fetch('/api/signature', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ meetingNumber, role: 0 }),
});
if (!sigRes.ok) throw new Error(`signature_fetch_failed:${sigRes.status}`);
const { signature, sdkKey } = await sigRes.json();
await client.init({
zoomAppRoot: document.getElementById('meetingSDKElement')!,
language: 'en-US',
patchJsMedia: true,
leaveOnPageUnload: true,
customize: {
video: { isResizable: true, popper: { disableDraggable: false } },
},
});
await client.join({
signature,
sdkKey,
meetingNumber,
userName,
password,
});
}
Common failure points:
- wrong route: Video SDK instead of Meeting SDK Component View
- missing backend signature endpoint
- wrong password field (
password here, not passWord)
- missing OBF/ZAK requirements for meetings outside the app account
- missing SharedArrayBuffer headers when higher-end meeting features are expected
Hard Routing Rule
If the user wants a custom video user interface for a Zoom meeting in a web app, route to
Component View, not Video SDK.
- Meeting SDK Component View = custom UI for a real Zoom meeting
- Video SDK Web = custom UI for a non-meeting video session product
For the direct custom-meeting-UI path, start with
component-view/SKILL.md.
New to Web SDK? Start Here!
The fastest way to master the SDK:
- Choose Your View - Client View vs Component View - Understand the key architectural differences
- Quick Start - Client View or Component View - Get a working meeting in minutes
- SharedArrayBuffer - concepts/sharedarraybuffer.md - Required for HD video, gallery view, virtual backgrounds
- Optional preflight diagnostics - ../../probe-sdk/SKILL.md - Validate browser/device/network before join
Building a Custom Integration?
Having issues?
- Join errors → Check signature generation and password spelling (
passWord vs password)
- HD video not working → Enable SharedArrayBuffer headers
- Complete navigation → SKILL.md
Prerequisites
- Zoom app with Meeting SDK credentials from Marketplace
- SDK Key (Client ID) and Secret
- Modern browser (Chrome, Firefox, Safari, Edge)
- Backend auth endpoint for signature generation
Need help with authentication? See the zoom-oauth skill for JWT/signature generation.
Want pre-join diagnostics? Chain probe-sdk before init()/join() to gate low-readiness environments.
Optional Preflight Gate (Probe SDK)
For unstable first-join environments, run Probe SDK checks before calling ZoomMtg.init() or client.join():
- Run Probe permissions/device/network diagnostics.
- Apply readiness policy (
allow, warn, block).
- Continue to Meeting SDK join only for
allow/approved warn.
See ../../probe-sdk/SKILL.md and ../../general/use-cases/probe-sdk-preflight-readiness-gate.md.
Client View vs Component View
CRITICAL DIFFERENCE: These are two completely different APIs with different patterns!
| Aspect | Client View | Component View |
|---|
| Object | ZoomMtg (global singleton) | ZoomMtgEmbedded.createClient() (instance) |
| API Style | Callbacks | Promises |
| UI | Full-page takeover | Embeddable in any container |
| Password param | passWord (capital W) | password (lowercase) |
| Events | inMeetingServiceListener() | on()/off() |
| Import (npm) | import { ZoomMtg } from '@zoom/meetingsdk' | import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded' |
| CDN | zoom-meeting-{VERSION}.min.js | zoom-meeting-embedded-{VERSION}.min.js |
| Best For | Quick integration, standard Zoom UI | Custom layouts, React/Vue apps |
When to Use Which
Use Client View when:
- You want the familiar Zoom meeting interface
- Quick integration is priority over customization
- Full-page meeting experience is acceptable
Use Component View when:
- You need to embed meetings in a specific area of your page
- Building React/Vue/Angular applications
- You want Promise-based async/await syntax
- Custom positioning and resizing is required
Installation
NPM (Recommended)
npm install @zoom/meetingsdk --save
CDN
<script src="https://source.zoom.us/{VERSION}/lib/vendor/react.min.js"></script>
<script src="https://source.zoom.us/{VERSION}/lib/vendor/react-dom.min.js"></script>
<script src="https://source.zoom.us/{VERSION}/lib/vendor/redux.min.js"></script>
<script src="https://source.zoom.us/{VERSION}/lib/vendor/redux-thunk.min.js"></script>
<script src="https://source.zoom.us/{VERSION}/lib/vendor/lodash.min.js"></script>
<script src="https://source.zoom.us/zoom-meeting-{VERSION}.min.js"></script>
<script src="https://source.zoom.us/zoom-meeting-embedded-{VERSION}.min.js"></script>
Replace {VERSION} with the latest version (e.g., 3.11.0).
Quick Start (Client View)
import { ZoomMtg } from '@zoom/meetingsdk';
console.log('System requirements:', ZoomMtg.checkSystemRequirements());
ZoomMtg.preLoadWasm();
ZoomMtg.prepareWebSDK();
ZoomMtg.i18n.load('en-US');
ZoomMtg.i18n.onLoad(() => {
ZoomMtg.init({
leaveUrl: 'https://yoursite.com/meeting-ended',
disableCORP: !window.crossOriginIsolated,
patchJsMedia: true,
leaveOnPageUnload: true,
externalLinkPage: './external.html',
success: () => {
ZoomMtg.join({
signature: signature,
meetingNumber: '1234567890',
userName: 'User Name',
passWord: 'meeting-password',
success: (res) => {
console.log('Joined meeting:', res);
ZoomMtg.getAttendeeslist({});
ZoomMtg.getCurrentUser({
success: (res) => console.log('Current user:', res.result.currentUser)
});
},
error: (err) => {
console.error('Join error:', err);
}
});
},
error: (err) => {
console.error('Init error:', err);
}
});
});
Quick Start (Component View)
import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';
const client = ZoomMtgEmbedded.createClient();
async function startMeeting() {
try {
await client.init({
zoomAppRoot: document.getElementById('meetingSDKElement'),
language: 'en-US',
debug: true,
patchJsMedia: true,
leaveOnPageUnload: true,
});
await client.join({
signature: signature,
sdkKey: SDK_KEY,
meetingNumber: '1234567890',
userName: 'User Name',
password: 'meeting-password',
});
console.log('Joined successfully!');
} catch (error) {
console.error('Failed to join:', error);
}
}
Authentication Endpoint (Required)
Both views require a JWT signature from a backend server. Never expose your SDK Secret in frontend code!
git clone https://github.com/zoom/meetingsdk-auth-endpoint-sample --depth 1
cd meetingsdk-auth-endpoint-sample
cp .env.example .env
npm install && npm run start
Signature Generation
The signature encodes:
sdkKey (or clientId for newer apps)
meetingNumber
role (0 = participant, 1 = host)
iat (issued at timestamp)
exp (expiration timestamp)
tokenExp (token expiration)
IMPORTANT (March 2026): Apps joining meetings outside their account will require an App Privilege Token (OBF) or ZAK token. See Authorization Requirements.
Core Workflow
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Get Signature │───►│ init() │───►│ join() │
│ (from backend)│ │ (SDK setup) │ │ (enter mtg) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
success/error success/error
callback callback
(or Promise resolve) (or Promise resolve)
Client View API Reference
ZoomMtg.init() - Key Options
ZoomMtg.init({
leaveUrl: string,
showMeetingHeader: boolean,
disableInvite: boolean,
disableRecord: boolean,
disableJoinAudio: boolean,
disablePreview: boolean,
enableHD: boolean,
enableFullHD: boolean,
defaultView: 'gallery' | 'speaker' | 'multiSpeaker',
isSupportChat: boolean,
isSupportCC: boolean,
isSupportBreakout: boolean,
isSupportPolling: boolean,
isSupportQA: boolean,
disableCORP: boolean,
success: Function,
error: Function,
});
ZoomMtg.join() - Key Options
ZoomMtg.join({
signature: string,
meetingNumber: string | number,
userName: string,
passWord: string,
zak: string,
tk: string,
obfToken: string,
userEmail: string,
customerKey: string,
success: Function,
error: Function,
});
Event Listeners (Client View)
ZoomMtg.inMeetingServiceListener('onUserJoin', (data) => {
console.log('User joined:', data);
});
ZoomMtg.inMeetingServiceListener('onUserLeave', (data) => {
console.log('User left:', data);
});
ZoomMtg.inMeetingServiceListener('onUserUpdate', (data) => {
console.log('User updated:', data);
});
ZoomMtg.inMeetingServiceListener('onMeetingStatus', (data) => {
console.log('Meeting status:', data.status);
});
ZoomMtg.inMeetingServiceListener('onUserIsInWaitingRoom', (data) => {
console.log('User in waiting room:', data);
});
ZoomMtg.inMeetingServiceListener('onActiveSpeaker', (data) => {
console.log('Active speaker:', data);
});
ZoomMtg.inMeetingServiceListener('onNetworkQualityChange', (data) => {
if (data.level <= 1) {
console.warn('Poor network quality');
}
});
ZoomMtg.inMeetingServiceListener('onJoinSpeed', (data) => {
console.log('Join speed metrics:', data);
});
ZoomMtg.inMeetingServiceListener('onReceiveChatMsg', (data) => {
console.log('Chat message:', data);
});
ZoomMtg.inMeetingServiceListener('onRecordingChange', (data) => {
console.log('Recording status:', data);
});
ZoomMtg.inMeetingServiceListener('onShareContentChange', (data) => {
console.log('Share content changed:', data);
});
ZoomMtg.inMeetingServiceListener('onReceiveTranscriptionMsg', (data) => {
console.log('Transcription:', data);
});
ZoomMtg.inMeetingServiceListener('onRoomStatusChange', (data) => {
console.log('Breakout room status:', data);
});
Common Methods (Client View)
ZoomMtg.getCurrentUser({
success: (res) => console.log(res.result.currentUser)
});
ZoomMtg.getAttendeeslist({});
ZoomMtg.mute({ userId, mute: true });
ZoomMtg.muteAll({ muteAll: true });
ZoomMtg.sendChat({ message: 'Hello!', userId: 0 });
ZoomMtg.leaveMeeting({});
ZoomMtg.endMeeting({});
ZoomMtg.makeHost({ userId });
ZoomMtg.makeCoHost({ oderId });
ZoomMtg.expel({ userId });
ZoomMtg.putOnHold({ oderId, bHold: true });
ZoomMtg.createBreakoutRoom({ rooms: [...] });
ZoomMtg.openBreakoutRooms({});
ZoomMtg.closeBreakoutRooms({});
ZoomMtg.setVirtualBackground({ imageUrl: '...' });
Component View API Reference
client.init() - Key Options
await client.init({
zoomAppRoot: HTMLElement,
language: string,
debug: boolean,
patchJsMedia: boolean,
leaveOnPageUnload: boolean,
enableHD: boolean,
enableFullHD: boolean,
customize: {
video: {
isResizable: boolean,
viewSizes: { default: { width, height } }
},
meetingInfo: ['topic', 'host', 'mn', 'pwd', 'telPwd', 'invite', 'participant', 'dc', 'enctype'],
toolbar: {
buttons: [
{
text: 'Custom Button',
className: 'custom-btn',
onClick: () => {
console.log('Custom button clicked');
}
}
]
}
},
webEndpoint: string,
assetPath: string,
});
client.join() - Key Options
await client.join({
signature: string,
sdkKey: string,
meetingNumber: string | number,
userName: string,
password: string,
zak: string,
tk: string,
userEmail: string,
});
Event Listeners (Component View)
client.on('connection-change', (payload) => {
console.log('Connection:', payload.state);
});
client.on('user-added', (payload) => {
console.log('Users added:', payload);
});
client.on('user-removed', (payload) => {
console.log('Users removed:', payload);
});
client.on('user-updated', (payload) => {
console.log('Users updated:', payload);
});
client.on('active-speaker', (payload) => {
console.log('Active speaker:', payload);
});
client.on('video-active-change', (payload) => {
console.log('Video active:', payload);
});
client.off('connection-change', handler);
Common Methods (Component View)
const currentUser = client.getCurrentUser();
const participants = client.getParticipantsList();
await client.mute(true);
await client.muteAudio(userId, true);
await client.muteVideo(userId, true);
client.leaveMeeting();
client.endMeeting();
SharedArrayBuffer (CRITICAL for HD)
SharedArrayBuffer enables advanced features:
- 720p/1080p video
- Gallery view
- Virtual backgrounds
- Background noise suppression
Enable with HTTP Headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Verify in Browser
if (typeof SharedArrayBuffer === 'function') {
console.log('SharedArrayBuffer enabled!');
} else {
console.warn('HD features will be limited');
}
console.log('Cross-origin isolated:', window.crossOriginIsolated);
Platform-Specific Setup
See concepts/sharedarraybuffer.md for:
- Vercel, Netlify, AWS CloudFront configuration
- nginx/Apache configuration
- Service worker fallback for GitHub Pages
Development Setup (Two-Server Pattern)
The official samples use a two-server pattern for development because COOP/COEP headers can break navigation:
proxy: [{
path: '/meeting.html',
target: 'http://YOUR_MEETING_SERVER_HOST:9998/'
}]
Vite config with headers:
export default defineConfig({
server: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
}
}
});
Common Issues & Solutions
| Issue | Solution |
|---|
| Join fails with signature error | Verify signature generation, check sdkKey format |
| "passWord" typo | Client View uses passWord (capital W), Component View uses password |
| No HD video | Enable SharedArrayBuffer headers, check browser support |
| Callbacks not firing | Ensure inMeetingServiceListener called after init success |
| Virtual background not working | Requires SharedArrayBuffer + Chrome/Edge |
| Screen share fails on Safari | Safari 17+ with macOS 14+ required for client view |
Complete troubleshooting: troubleshooting/common-issues.md
Browser Support Matrix
| Feature | Chrome | Firefox | Safari | Edge | iOS | Android |
|---|
| 720p (receive) | Yes | Yes | Yes | Yes | Yes | Yes |
| 720p (send) | Yes* | Yes* | Yes* | Yes* | Yes* | Yes* |
| Virtual background | Yes | Yes | No | Yes | No | No |
| Screen share (send) | Yes | Yes | Safari 17+ | Yes | No | No |
| Gallery view | Yes | Yes | Yes** | Yes | Yes | Yes |
*Requires SharedArrayBuffer
**Safari 17+ with macOS Sonoma
See concepts/browser-support.md for complete matrix.
Authorization Requirements (2026 Update)
IMPORTANT: Beginning March 2, 2026, apps joining meetings outside their account must be authorized.
Options
-
App Privilege Token (OBF) - Recommended for bots
ZoomMtg.join({
...
obfToken: 'your-app-privilege-token'
});
-
ZAK Token - For host operations
ZoomMtg.join({
...
zak: 'host-zak-token'
});
Zoom for Government (ZFG)
Option 1: ZFG-specific NPM Package
{
"dependencies": {
"@zoom/meetingsdk": "3.11.2-zfg"
}
}
Option 2: Configure ZFG Endpoints
Client View:
ZoomMtg.setZoomJSLib('https://source.zoomgov.com/{VERSION}/lib', '/av');
ZoomMtg.init({
webEndpoint: 'www.zoomgov.com',
...
});
Component View:
await client.init({
webEndpoint: 'www.zoomgov.com',
assetPath: 'https://source.zoomgov.com/{VERSION}/lib/av',
...
});
China CDN
ZoomMtg.setZoomJSLib('https://jssdk.zoomus.cn/{VERSION}/lib', '/av');
React Integration
Official Pattern (from zoom/meetingsdk-react-sample)
The official React sample uses imperative initialization rather than React hooks:
import { ZoomMtg } from '@zoom/meetingsdk';
ZoomMtg.preLoadWasm();
ZoomMtg.prepareWebSDK();
function App() {
const authEndpoint = import.meta.env.VITE_AUTH_ENDPOINT;
const meetingNumber = '';
const passWord = '';
const role = 0;
const userName = 'React User';
const getSignature = async () => {
const response = await fetch(authEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
meetingNumber,
role,
}),
});
const data = await response.json();
startMeeting(data.signature);
};
const startMeeting = (signature: string) => {
document.getElementById('zmmtg-root')!.style.display = 'block';
ZoomMtg.init({
leaveUrl: window.location.origin,
patchJsMedia: true,
leaveOnPageUnload: true,
success: () => {
ZoomMtg.join({
signature,
meetingNumber,
userName,
passWord,
success: (res) => console.log('Joined:', res),
error: (err) => console.error('Join error:', err),
});
},
error: (err) => console.error('Init error:', err),
});
};
return (
<button onClick={getSignature}>Join Meeting</button>
);
}
React Gotchas (from official samples)
| Issue | Problem | Solution |
|---|
| Client Recreation | createClient() in component body runs every render | Use useRef to persist client |
| No useEffect | Official sample doesn't use React lifecycle hooks | SDK's leaveOnPageUnload handles cleanup |
| Direct DOM | Sample uses getElementById | Use useRef<HTMLDivElement> in production |
| No Error State | Silent failures | Add useState for error handling |
| Module-Scope Side Effects | preLoadWasm() at top level | May cause issues with SSR |
Production-Ready React Pattern
import { useEffect, useRef, useState, useCallback } from 'react';
import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';
type ZoomClient = ReturnType<typeof ZoomMtgEmbedded.createClient>;
function ZoomMeeting({ meetingNumber, password, userName }: Props) {
const clientRef = useRef<ZoomClient | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [isJoining, setIsJoining] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!clientRef.current) {
clientRef.current = ZoomMtgEmbedded.createClient();
}
}, []);
const joinMeeting = useCallback(async () => {
if (!clientRef.current || !containerRef.current) return;
setIsJoining(true);
setError(null);
try {
const response = await fetch('/api/signature', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ meetingNumber, role: 0 }),
});
const { signature, sdkKey } = await response.json();
await clientRef.current.init({
zoomAppRoot: containerRef.current,
language: 'en-US',
patchJsMedia: true,
leaveOnPageUnload: true,
});
await clientRef.current.join({
signature,
sdkKey,
meetingNumber,
password,
userName,
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to join');
} finally {
setIsJoining(false);
}
}, [meetingNumber, password, userName]);
return (
<div>
<div ref={containerRef} style={{ width: '100%', height: '500px' }} />
<button onClick={joinMeeting} disabled={isJoining}>
{isJoining ? 'Joining...' : 'Join Meeting'}
</button>
{error && <div className="error">{error}</div>}
</div>
);
}
Environment Variables (Vite)
VITE_AUTH_ENDPOINT=http://YOUR_AUTH_SERVER_HOST:4000
VITE_SDK_KEY=your_sdk_key
const authEndpoint = import.meta.env.VITE_AUTH_ENDPOINT;
const sdkKey = import.meta.env.VITE_SDK_KEY;
Detailed References
Core Documentation
Concepts
Troubleshooting
Examples
Helper Utilities
Extract Meeting Number from Invite Link
document.getElementById('meeting_number').addEventListener('input', (e) => {
let meetingNumber = e.target.value.replace(/([^0-9])+/i, '');
if (meetingNumber.match(/([0-9]{9,11})/)) {
meetingNumber = meetingNumber.match(/([0-9]{9,11})/)[1];
}
const pwdMatch = e.target.value.match(/pwd=([\d,\w]+)/);
if (pwdMatch) {
document.getElementById('password').value = pwdMatch[1];
}
});
Dynamic Language Switching
document.getElementById('language').addEventListener('change', (e) => {
const lang = e.target.value;
ZoomMtg.i18n.load(lang);
ZoomMtg.i18n.reload(lang);
ZoomMtg.reRender({ lang });
});
Check System Requirements
const requirements = ZoomMtg.checkSystemRequirements();
console.log('Browser info:', JSON.stringify(requirements));
if (!requirements.browserInfo.isChrome && !requirements.browserInfo.isFirefox) {
alert('For best experience, use Chrome or Firefox');
}
Sample Repositories
Official Resources
Documentation Version: Based on Zoom Web Meeting SDK v3.11+
Need help? Start with SKILL.md for complete navigation.
Merged from meeting-sdk/web/SKILL.md
Zoom Meeting SDK (Web) - Documentation Index
Quick navigation guide for all Web SDK documentation.
Start Here
| Document | Description |
|---|
| SKILL.md | Main entry point - Quick starts for both Client View and Component View |
By View Type
Client View (Full-Page)
Component View (Embeddable)
Concepts
Examples
Troubleshooting
By Topic
Authentication
HD Video & Performance
Events & Callbacks
Government (ZFG)
China CDN
Quick Reference
Client View vs Component View
| Aspect | Client View | Component View |
|---|
| Object | ZoomMtg | ZoomMtgEmbedded.createClient() |
| API Style | Callbacks | Promises |
| Password param | passWord (capital W) | password (lowercase) |
| Events | inMeetingServiceListener() | on()/off() |
Key Gotchas
-
Password spelling differs between views!
- Client View:
passWord (capital W)
- Component View:
password (lowercase)
-
SharedArrayBuffer required for HD features
- 720p/1080p video
- Gallery view (25 videos)
- Virtual backgrounds
-
March 2026 Authorization Change
- Apps joining external meetings need OBF or ZAK tokens
External Resources
Operations
- RUNBOOK.md - 5-minute preflight and debugging checklist.