Zoom Apps SDK
OpenAIOfficialCreated by ZoomDistributed by OpenAI
- Skill
- Tool integration
- Software development
- MIT
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Zoom Apps SDK skill provides a structured approach for developers building applications that run directly inside the Zoom client. It covers the lifecycle of a Zoom App, from initial configuration and context verification to implementing advanced features like the Layers API, in-client OAuth, and inter-instance communication. The skill emphasizes the importance of identifying the correct running context—such as meetings, webinars, or the main client—to ensure features are gated appropriately and the user experience remains consistent across different Zoom environments.
What it does
- Initializes the Zoom Apps SDK with required capabilities.
- Identifies the current running context, such as meeting or main client.
- Facilitates in-client OAuth flows for secure user authentication.
- Enables communication between different app instances.
- Provides access to meeting-specific data and participant information.
- Supports advanced UI and media features like virtual backgrounds and notification management.
When to use it
- When building an application intended to run inside the Zoom client interface.
- When you need to access meeting context, participant lists, or recording status.
- When implementing custom UI overlays or media controls within a Zoom meeting.
- When you need to authenticate users without redirecting them to an external browser.
Advantages
- Provides a unified API for interacting with various Zoom client contexts.
- Supports secure, in-client authentication workflows.
- Enables deep integration with Zoom meeting features and participant data.
- Offers clear patterns for handling cross-instance communication.
Usage examples
- “How do I initialize the Zoom Apps SDK?”
- You must call zoomSdk.config() first, passing in the required capabilities and version to initialize the SDK and declare the features your app intends to use.
- “How can I get the current meeting ID?”
- After initializing the SDK, you can call zoomSdk.getMeetingContext() to retrieve the meetingID, meetingTopic, and meetingStatus.
- “How do I trigger authentication within the Zoom client?”
- Use the zoomSdk.authorize() method to trigger the in-client OAuth flow, which avoids external browser redirects and fires an onAuthorized event upon completion.
- “How do I send a message to another instance of my app?”
- First call zoomSdk.connect() to establish the connection, then use zoomSdk.postMessage() to send a payload to other app instances.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/zoom-apps-sdk/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip zoom-apps-sdk.zip -d .agents/skills
Documentation by Zoom
Zoom Apps SDK
Use this skill when the app runs inside the Zoom client. If the user only needs to embed a meeting in an external app, route to Meeting SDK instead.
Workflow
Confirm the running context: meeting, webinar, main client, phone, collaborate mode, immersive mode, camera mode, or Layers API.
Configure Marketplace app settings, allowed domains, redirect URIs, and in-client OAuth before implementing SDK calls.
Initialize
zoomSdkand gate features by capability and running context.Design client communication and data flow: frontend SDK calls, backend REST calls, in-client OAuth tokens, and webhook handoff.
Implement advanced client features only after the base app loads reliably: Layers API, breakout rooms, guest mode, collaborate mode, or ZMail.
Debug blank panels, domain allowlist issues, CSP, missing capabilities, running-context mismatch, and OAuth redirect problems independently.
References
Full preserved guide: references/full-guide.md
Architecture: concepts/architecture.md
Running contexts: concepts/running-contexts.md
Meeting SDK versus Zoom Apps: concepts/meeting-sdk-vs-zoom-apps.md
Layers API: references/layers-api.md
In-client OAuth: examples/in-client-oauth.md
Common issues: troubleshooting/common-issues.md
Files
- ATTRIBUTION.mdAdded by AutoGineer873 B
- LICENSELicence file added by AutoGineer1.1 KiB
- RUNBOOK.md3.3 KiB
- SKILL.md1.5 KiB
- agents/openai.yaml130 B
- concepts/architecture.md7.7 KiB
- concepts/meeting-sdk-vs-zoom-apps.md1.0 KiB
- concepts/running-contexts.md5.6 KiB
- concepts/security.md5.4 KiB
- examples/app-communication.md3.6 KiB
- examples/breakout-rooms.md3.2 KiB
- examples/collaborate-mode.md4.1 KiB
- examples/guest-mode.md3.5 KiB
- examples/in-client-oauth.md6.1 KiB
- examples/layers-camera.md5.7 KiB
- examples/layers-immersive.md7.9 KiB
- examples/quick-start.md7.3 KiB
- references/apis.md5.3 KiB
- references/environment-variables.md1.1 KiB
- references/events.md3.8 KiB
- references/full-guide.md24.9 KiB
- references/layers-api.md15.8 KiB
- references/oauth.md5.3 KiB
- references/zmail-sdk.md5.5 KiB
- troubleshooting/common-issues.md4.3 KiB
- troubleshooting/debugging.md3.5 KiB
- troubleshooting/forum-top-questions.md1.1 KiB
- troubleshooting/migration.md3.0 KiB
references/layers-api.md
Zoom Apps SDK - Layers API Reference
Build immersive video layouts and camera overlays using the Layers API.
Overview
The Layers API (v1.5) provides rendering modes for custom visual experiences. Requires Zoom Client v5.10.6+.
| Mode | Description | Use Case |
|---|---|---|
Team (immersive + person cutout) | Canvas with background-removed participant cutouts | Podcast, talk show, classroom |
Presentation (immersive + rectangle cutout) | Canvas with full-width participant video tiles | Presentations, branded meetings |
| Camera | Overlay on user's own camera feed (OSR) | Branding, name tags, effects |
| Controller | Sidebar app that coordinates Layers modes | Required for all modes above |
Note: When using the Layers API, your app is categorized as an "Immersive App" on the Marketplace.
Required Capabilities
await zoomSdk.config({
capabilities: [
'getRunningContext',
'runRenderingContext', 'closeRenderingContext',
'drawParticipant', 'clearParticipant',
'drawImage', 'clearImage',
'drawWebView', 'clearWebView',
'postMessage', 'onMessage',
'sendAppInvitationToAllParticipants',
'onMyMediaChange',
'onRenderedAppOpened'
],
version: '0.16'
});Gotcha: The official guide lists
clearWebview(lowercase 'v') in one config example. UseclearWebView(camelCase) to match the actual method name.
Types
PixelValue
All position/size parameters accept three formats:
type PixelValue = `${string}px` | `${string}%` | number;| Format | Example | Meaning |
|---|---|---|
"Npx" | "100px" | CSS reference pixels |
"N%" | "50%" | Percentage of container/view |
number | 1280 | Raw physical pixels |
ParticipantCutoutShape
type ParticipantCutoutShape =
| "person" // v5.9.3+ — Cut out background (AI segmentation)
| "standard" // v5.11.3+ — Full uncropped video (squared corners)
| "rectangle" // v5.11.0+ — Rounded rectangle (30px radius)
| "circle" // v5.11.3+ — Circle
| "square" // v5.11.3+ — Square (30px radius)
| "verticalRectangle" // v5.11.3+ — Vertical rectangle (30px radius)All shapes have 30px rounded corners except "standard" which has squared corners.
RenderingContextView
type RenderingContextView = "immersive" | "camera";Lifecycle
Starting a Rendering Context
// Team mode (person cutout — removes backgrounds)
await zoomSdk.runRenderingContext({
view: 'immersive',
defaultCutout: 'person'
});
// Presentation mode (rectangle cutout — keeps backgrounds)
await zoomSdk.runRenderingContext({
view: 'immersive',
defaultCutout: 'rectangle'
});
// Camera mode (affects only your video stream)
await zoomSdk.runRenderingContext({ view: 'camera' });runRenderingContext(options):
view(required):"immersive"|"camera"defaultCutout(optional): Sets the default cutout shape for alldrawParticipant()calls in this context
Running Context Values
| Context | Meaning |
|---|---|
inMeeting | Default sidebar panel |
inImmersive | Running in immersive mode (team or presentation) |
inCamera | Running as virtual camera (off-screen rendering) |
const { runningContext } = await zoomSdk.getRunningContext();
// runningContext changes automatically when runRenderingContext() is calledUpdating Content
To move, resize, or adjust a drawn element: clear it first, then redraw.
// Move a participant
await zoomSdk.clearParticipant({ participantUUID: uuid });
await zoomSdk.drawParticipant({ participantUUID: uuid, x: 100, y: 200, width: 640, height: 480, zIndex: 1 });There is no in-place update — always clear + redraw.
Closing
await zoomSdk.closeRenderingContext();
// Returns app to sidebar, runningContext becomes "inMeeting"Constraints
Only a meeting host can set the rendering context to immersive
Only one immersive context can exist at a time (second attempt fails with error)
Camera mode + Presentation mode can run simultaneously
Host must use
sendAppInvitationToAllParticipantsto transition other participantsIf
aomhostpackage needs download,runRenderingContextreturns non-success
Drawing Methods
drawParticipant
Position a participant's video feed on the canvas.
drawParticipant(options: DrawParticipantOptions): Promise<GeneralMessageResponse>| Parameter | Type | Default | Description |
|---|---|---|---|
participantUUID | string | — | Meeting-specific participant identifier |
participantId | string | — | DEPRECATED — use participantUUID |
x | PixelValue | "0px" | Horizontal position |
y | PixelValue | "0px" | Vertical position |
width | PixelValue | "100%" | Width (aspect ratio maintained) |
height | PixelValue | "100%" | Height (aspect ratio maintained) |
zIndex | number | 1 | Stacking order (higher = on top) |
cutout | ParticipantCutoutShape | context default | Cutout behavior (v5.9.3+) |
cameraModeMirroring | boolean | false | Mirror video in camera mode (v5.13.5+) |
Mode differences:
Immersive: Can draw any participant
Camera: Can only draw current user (self)
// Immersive — draw any participant with person cutout
await zoomSdk.drawParticipant({
participantUUID: 'uuid-from-getMeetingParticipants',
x: 40, y: 100,
width: 580, height: 500,
zIndex: 1,
cutout: 'person'
});
// Camera — draw self with mirroring
await zoomSdk.drawParticipant({
participantUUID: myUUID,
x: 0, y: 0,
width: 1280, height: 720,
zIndex: 1,
cameraModeMirroring: true // v5.13.5+
});drawImage
Draw static images (backgrounds, overlays, borders).
drawImage(options: DrawImageOptions): Promise<DrawImageResponse>| Parameter | Type | Default | Description |
|---|---|---|---|
imageData | ImageData | — | Required. Standard JS ImageData object (width, height, pixel bytes) |
x | PixelValue | "0px" | Horizontal position |
y | PixelValue | "0px" | Vertical position |
zIndex | number | 1 | Stacking order |
Returns: { imageId: string } — use this ID with clearImage().
Important:
imageDatais a standard JavaScriptImageDataobject (fromcanvas.getImageData()), NOT a base64 data URL.
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
// ... draw on canvas ...
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const { imageId } = await zoomSdk.drawImage({
imageData,
x: 0, y: 0,
zIndex: 0
});HiDPI Constraints
drawImage() does not directly support HiDPI image sizes. For HiDPI/Retina:
Draw to canvas using the scaling ratio (
window.devicePixelRatio)Divide out the ratio for x/y coordinates when passing to
drawImageKeep the ratio for width and height
You may need to tile the screen for full-screen images
const dpr = window.devicePixelRatio || 1;
const canvas = document.createElement('canvas');
canvas.width = 1280 * dpr;
canvas.height = 720 * dpr;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
// ... draw at logical pixels ...
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
await zoomSdk.drawImage({
imageData,
x: 0, y: 0,
zIndex: 0
});drawWebView
Position the app's OSR (Off-Screen Rendering) webview within the Layers canvas.
drawWebView(options: DrawWebViewOptions): Promise<GeneralMessageResponse>| Parameter | Type | Default | Description |
|---|---|---|---|
x | PixelValue | 0 | Horizontal position in OSR target area |
y | PixelValue | 0 | Vertical position in OSR target area |
width | PixelValue | full rendering width | Width in OSR target area |
height | PixelValue | full rendering height | Height in OSR target area |
zIndex | number | 1 | Stacking order |
⚠ Documentation inconsistency: The official Zoom guides show a
webviewIdparameter in examples, but the TypeDoc type definition (v0.16.36) does not include it. Since there is only one webview per app, this parameter may be vestigial. If in doubt, omit it.
What the webview renders: Your app's home URL as configured in zoomSdk.config(). It's an off-screen rendering of your app — not a configurable URL.
Only one webview per rendering context. There is no multi-webview support.
// Full-screen webview in camera mode
const config = await zoomSdk.config({ /* ... */ });
await zoomSdk.runRenderingContext({ view: 'camera' });
await zoomSdk.drawWebView({
x: 0,
y: 0,
width: config.media.renderTarget.width, // Default: 1280
height: config.media.renderTarget.height, // Default: 720
zIndex: 2
});// Partial webview overlay (bottom third of camera)
await zoomSdk.drawWebView({
x: 0,
y: 480,
width: 1280,
height: 240,
zIndex: 2
});Webview Communication
The sidebar app and the camera/immersive app are separate instances. Use postMessage() and onMessage to communicate between them:
// Sidebar instance → Camera instance (no connect() required)
zoomSdk.postMessage({ command: 'update-overlay', text: 'Q&A Time' });
// Camera instance listens
zoomSdk.addEventListener('onMessage', (eventInfo) => {
if (eventInfo.command === 'update-overlay') {
document.getElementById('overlay-text').textContent = eventInfo.text;
}
});Note:
connect()is NOT required for app-to-app messaging in Layers.postMessageworks between instances of the same app.
Clearing
// Clear participant (use participantUUID, not the deprecated participantId)
await zoomSdk.clearParticipant({ participantUUID: 'uuid' });
// Clear image (use imageId from drawImage response)
await zoomSdk.clearImage({ imageId: 'id-from-drawImage' });
// Clear webview (hides it — app continues running)
await zoomSdk.clearWebView();
// Note: TypeDoc v0.16.36 shows no parameters.
// Guide examples show { webviewId: "xxx" } but this may be outdated.Coordinate System
Origin: Top-left corner (0, 0)
X: Increases rightward
Y: Increases downward
Units: PixelValue — supports
"Npx","N%", or rawnumber
Immersive Mode
Coordinates are CSS pixels relative to the meeting canvas
Automatic scaling for different window sizes
Camera Mode
Coordinates are raw pixels relative to
renderTargetdimensionsDefault renderTarget: 1280×720 (configurable)
Access via:
config.media.renderTarget.width/.height
const config = await zoomSdk.config({ /* ... */ });
const rtWidth = config.media.renderTarget.width; // e.g. 1280
const rtHeight = config.media.renderTarget.height; // e.g. 720Z-Index Layering
zIndex: 2+ ─ WebViews, interactive overlays (top)
zIndex: 1 ─ Participant videos
zIndex: 0 ─ Background images (bottom)Higher zIndex values render on top. All three element types (participant, image, webview) share the same z-index space and can overlap.
Events
onRenderedAppOpened
Fires when the rendering context is ready. Best signal that CEF is initialized in camera mode.
zoomSdk.addEventListener('onRenderedAppOpened', () => {
// Safe to call drawParticipant, drawImage, drawWebView
});onMyMediaChange
Fires when the user's video changes (camera switch, "Original ratio" toggle, "HD" toggle). Returns device pixel dimensions of the source video.
zoomSdk.addEventListener('onMyMediaChange', (event) => {
// event.media.video.width / height — device pixels of source video
// Redraw your layout if needed
});Window Resize (Immersive Only)
When the Zoom meeting window is resized, the app must move and resize participants/images. Not relevant to Camera Mode (fixed renderTarget).
Immersive Mode vs Camera Mode
| Aspect | Immersive | Camera |
|---|---|---|
| Scope | Entire meeting view | User's camera only |
| drawParticipant | Any participant | Self only |
| drawImage | Yes | Yes |
| drawWebView | Yes | Yes |
| Who sees it | All participants | All see it on this user's feed |
| Browser engine | Standard WebView | CEF (Chromium Embedded Framework) |
| Rendering | On-screen | Off-screen (OSR) |
| Coordinate space | CSS pixels | Raw pixels (renderTarget) |
| Simultaneous | One immersive at a time | Can run with Presentation mode |
Camera Mode: CEF Race Condition
Camera mode uses CEF which takes time to initialize. Draw calls may fail if called too early.
Best approach: Listen for onRenderedAppOpened:
zoomSdk.addEventListener('onRenderedAppOpened', async () => {
await zoomSdk.drawWebView({ x: 0, y: 0, width: 1280, height: 720, zIndex: 2 });
});Fallback: Retry with exponential backoff:
async function drawWithRetry(drawFn, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
await drawFn();
return;
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(r => setTimeout(r, 200 * Math.pow(2, i)));
}
}
}Alternative: Check running context:
const { runningContext } = await zoomSdk.getRunningContext();
if (runningContext === 'inCamera') {
// CEF is ready, safe to draw
}Performance Tips
Use
requestAnimationFramefor animationsMinimize draw calls (batch updates when possible)
Pre-render complex backgrounds to a single canvas ImageData
Keep zIndex values low (0-10 range)
Clear unused elements to free resources
Test on lower-end hardware
For full-screen images: tile the screen (HiDPI limitation)
Example: Two-Person Podcast Layout
// Background
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 1280, 720);
gradient.addColorStop(0, '#1a1a2e');
gradient.addColorStop(1, '#16213e');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1280, 720);
const imageData = ctx.getImageData(0, 0, 1280, 720);
await zoomSdk.drawImage({ imageData, x: 0, y: 0, zIndex: 0 });
// Host (left) — person cutout removes background
await zoomSdk.drawParticipant({
participantUUID: hostUUID,
x: 40, y: 100, width: 580, height: 500,
zIndex: 1, cutout: 'person'
});
// Guest (right)
await zoomSdk.drawParticipant({
participantUUID: guestUUID,
x: 660, y: 100, width: 580, height: 500,
zIndex: 1, cutout: 'person'
});Version History
| Feature | Client Version | SDK Version |
|---|---|---|
| Core Layers API | 5.9.0 | 0.16 |
cutout: "person" | 5.9.3 | 0.16 |
cutout: "rectangle" | 5.11.0 | 0.16 |
cutout: "circle", "square", "verticalRectangle" | 5.11.3 | 0.16 |
drawWebView() / clearWebView() | 5.10.6 | 0.16.11+ |
| Camera Mode | 5.13.1 | 0.16 |
cameraModeMirroring | 5.13.5 | 0.16 |
Resources
Layers API docs: https://developers.zoom.us/docs/zoom-apps/guides/layers-api/
Using the API: https://developers.zoom.us/docs/zoom-apps/guides/layers-using-api/
Manipulating UI: https://developers.zoom.us/docs/zoom-apps/guides/layers-manipulating-ui/
Camera Mode docs: https://developers.zoom.us/docs/zoom-apps/guides/camera-mode/
Sample app: https://github.com/zoom/zoomapps-customlayout-js
SDK TypeDoc: https://appssdk.zoom.us/classes/ZoomSdk.ZoomSdk.html
Immersive example: ../examples/layers-immersive.md
Camera example: ../examples/layers-camera.md
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Medium risk
- Possible data-exfiltration endpoints
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Zoom
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/zoom/skills/zoom-apps-sdk · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/zoom/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Zoom or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download zoom-apps-sdk.zip