Zoom Meeting SDK Web - Component View. Embeddable Zoom meeting components with Promise-based API for flexible integration. Ideal for React/Vue/Angular apps and custom layouts. Uses ZoomMtgEmbedded with async/await patterns and embeddable UI containers.
56
66%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./plugins/zoom/skills/meeting-sdk/web/component-view/SKILL.mdEmbeddable Zoom meeting components for flexible integration into any web application. Component View provides Promise-based APIs and customizable UI.
This is the correct web skill for a custom UI around a real Zoom meeting. Do not route to Video SDK unless the user is building a non-meeting custom session product.
Component View uses ZoomMtgEmbedded.createClient() to create embeddable meeting components within a specific container element.
| Aspect | Details |
|---|---|
| API Object | ZoomMtgEmbedded.createClient() (instance) |
| API Style | Promise-based (async/await) |
| UI | Embeddable in any container |
| Password param | password (lowercase) |
| Events | on()/off() |
| Best For | Custom layouts, React/Vue/Angular apps |
npm install @zoom/meetingsdk --saveimport ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';<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-embedded-{VERSION}.min.js"></script>import ZoomMtgEmbedded from '@zoom/meetingsdk/embedded';
// Step 1: Create client instance (do once, not on every render!)
const client = ZoomMtgEmbedded.createClient();
async function joinMeeting() {
try {
// Step 2: Get container element
const meetingSDKElement = document.getElementById('meetingSDKElement');
// Step 3: Initialize client
await client.init({
zoomAppRoot: meetingSDKElement,
language: 'en-US',
debug: true,
patchJsMedia: true,
leaveOnPageUnload: true,
});
// Step 4: Join meeting
await client.join({
signature: signature,
sdkKey: sdkKey,
meetingNumber: meetingNumber,
userName: userName,
password: password, // lowercase!
userEmail: userEmail,
});
console.log('Joined successfully!');
} catch (error) {
console.error('Failed to join:', error);
}
}| Parameter | Type | Description |
|---|---|---|
zoomAppRoot | HTMLElement | Container element for meeting UI |
| Parameter | Type | Default | Description |
|---|---|---|---|
language | string | 'en-US' | UI language |
debug | boolean | false | Enable debug logging |
| Parameter | Type | Default | Description |
|---|---|---|---|
patchJsMedia | boolean | false | Auto-apply media fixes |
leaveOnPageUnload | boolean | false | Cleanup on page unload |
enableHD | boolean | true | Enable 720p video |
enableFullHD | boolean | false | Enable 1080p video |
| Parameter | Type | Description |
|---|---|---|
customize | object | UI customization options |
webEndpoint | string | For ZFG: 'www.zoomgov.com' |
assetPath | string | Custom path for AV libraries |
await client.init({
zoomAppRoot: element,
customize: {
// Meeting info displayed
meetingInfo: [
'topic',
'host',
'mn',
'pwd',
'telPwd',
'invite',
'participant',
'dc',
'enctype'
],
// Video customization
video: {
isResizable: true,
viewSizes: {
default: {
width: 1000,
height: 600
},
ribbon: {
width: 300,
height: 700
}
},
popper: {
disableDraggable: false
}
},
// Custom toolbar buttons
toolbar: {
buttons: [
{
text: 'Custom Button',
className: 'custom-btn',
onClick: () => {
console.log('Custom button clicked');
}
}
]
},
// Active speaker indicator
activeSpaker: {
strokeColor: '#00FF00'
}
}
});| Parameter | Type | Description |
|---|---|---|
signature | string | SDK JWT from backend |
sdkKey | string | SDK Key / Client ID |
meetingNumber | string | number | Meeting number |
userName | string | Display name |
| Parameter | Type | When Required | Description |
|---|---|---|---|
password | string | If set | Meeting password (lowercase!) |
zak | string | Starting as host | Host's ZAK token |
tk | string | Registration | Registrant token |
userEmail | string | Webinars | User email |
// Subscribe
client.on('event-name', callback);
// Unsubscribe
client.off('event-name', callback);client.on('connection-change', (payload) => {
// payload.state: 'Connecting', 'Connected', 'Reconnecting', 'Closed'
console.log('Connection state:', payload.state);
if (payload.state === 'Closed') {
console.log('Reason:', payload.reason);
}
});client.on('user-added', (payload) => {
// Array of users who joined
console.log('Users added:', payload);
payload.forEach(user => {
console.log('User ID:', user.oderId);
console.log('Name:', user.displayName);
});
});
client.on('user-removed', (payload) => {
// Array of users who left
console.log('Users removed:', payload);
});
client.on('user-updated', (payload) => {
// Array of users whose properties changed
console.log('Users updated:', payload);
});client.on('active-speaker', (payload) => {
// Current active speaker
console.log('Active speaker:', payload);
});
client.on('audio-statistic-data-change', (payload) => {
console.log('Audio stats:', payload);
});client.on('video-active-change', (payload) => {
// Video state changed
console.log('Video active:', payload);
});
client.on('video-statistic-data-change', (payload) => {
console.log('Video stats:', payload);
});client.on('active-share-change', (payload) => {
console.log('Share status:', payload);
});
client.on('share-statistic-data-change', (payload) => {
console.log('Share stats:', payload);
});client.on('chat-on-message', (payload) => {
console.log('Chat message:', payload);
});client.on('recording-change', (payload) => {
console.log('Recording status:', payload);
});client.on('media-sdk-change', (payload) => {
console.log('Media SDK:', payload);
});
client.on('device-change', () => {
console.log('Device changed');
});// Get current user
const currentUser = client.getCurrentUser();
console.log('Current user:', currentUser);
// Get all participants
const participants = client.getParticipantsList();
console.log('Participants:', participants);
// Check if user is host
const isHost = client.isHost();// Mute/unmute self
await client.mute(true); // mute
await client.mute(false); // unmute
// Mute/unmute specific user (host only)
await client.muteAudio(userId, true);
// Mute all (host only)
await client.muteAllAudio(true);// Start/stop video
await client.startVideo();
await client.stopVideo();
// Mute/unmute user's video (host only)
await client.muteVideo(userId, true);// Leave meeting
client.leaveMeeting();
// End meeting (host only)
client.endMeeting();// Start screen share
await client.startShareScreen();
// Stop screen share
await client.stopShareScreen();// Start recording (cloud)
await client.startCloudRecording();
// Stop recording
await client.stopCloudRecording();// Check support
const isSupported = await client.isSupportVirtualBackground();
// Set virtual background
await client.setVirtualBackground(imageUrl);
// Remove virtual background
await client.removeVirtualBackground();// Rename user
await client.rename(userId, 'New Name');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 [isJoined, setIsJoined] = useState(false);
const [error, setError] = useState<string | null>(null);
// Create client once
useEffect(() => {
if (!clientRef.current) {
clientRef.current = ZoomMtgEmbedded.createClient();
}
}, []);
const joinMeeting = useCallback(async () => {
if (!clientRef.current || !containerRef.current) return;
try {
// Get signature from backend
const { signature, sdkKey } = await fetchSignature(meetingNumber);
await clientRef.current.init({
zoomAppRoot: containerRef.current,
language: 'en-US',
patchJsMedia: true,
leaveOnPageUnload: true,
});
await clientRef.current.join({
signature,
sdkKey,
meetingNumber,
password,
userName,
});
setIsJoined(true);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to join');
}
}, [meetingNumber, password, userName]);
return (
<div>
<div
ref={containerRef}
style={{ width: '100%', height: '500px' }}
/>
{!isJoined && (
<button onClick={joinMeeting}>Join Meeting</button>
)}
{error && <div className="error">{error}</div>}
</div>
);
}useEffect(() => {
if (!clientRef.current) return;
const handleConnectionChange = (payload: any) => {
if (payload.state === 'Connected') {
setIsJoined(true);
} else if (payload.state === 'Closed') {
setIsJoined(false);
}
};
const handleUserAdded = (payload: any) => {
console.log('Users joined:', payload);
};
clientRef.current.on('connection-change', handleConnectionChange);
clientRef.current.on('user-added', handleUserAdded);
return () => {
clientRef.current?.off('connection-change', handleConnectionChange);
clientRef.current?.off('user-added', handleUserAdded);
};
}, []);await client.init({
zoomAppRoot: element,
customize: {
video: {
viewSizes: {
default: { width: 1000, height: 600 }
}
}
}
});The container element size determines the meeting UI size. To resize:
// Just resize the container
document.getElementById('meetingSDKElement').style.width = '1200px';
document.getElementById('meetingSDKElement').style.height = '800px';customize: {
video: {
isResizable: true
}
}Component View supports core meeting functionality. Some features from Client View may not be available.
| Feature | Supported |
|---|---|
| Audio/Video | ✅ |
| Screen Share | ✅ |
| Chat | ✅ |
| Virtual Background | ✅ |
| Breakout Rooms | ✅ |
| Cloud Recording | ✅ |
| Closed Captions | ✅ |
| Live Transcription | ✅ |
| Waiting Room | ✅ |
| Gallery View | ✅ |
| Reactions | ✅ |
| Raise Hand | ✅ |
Contact Zoom Developer Support to request additional features.
try {
await client.join({
// ... options
});
} catch (error) {
// error.reason contains error code
// error.message contains description
switch (error.reason) {
case 'WRONG_MEETING_PASSWORD':
console.error('Incorrect password');
break;
case 'MEETING_NOT_START':
console.error('Meeting has not started');
break;
case 'INVALID_PARAMETERS':
console.error('Invalid join parameters');
break;
default:
console.error('Join failed:', error.message);
}
}| Feature | Component View | Client View |
|---|---|---|
| API Style | Promises | Callbacks |
| Password param | password | passWord |
| Container | Custom element | Auto #zmmtg-root |
| UI | Embeddable | Full-page |
| Preloading | Not needed | preLoadWasm() |
| Language | Init option | i18n.load() |
| Events | on()/off() | inMeetingServiceListener() |
1e28582
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.