Viewer SDK¶
The Maverick Excelsior Viewer SDK puts an interactive .webex scene inside your web page. You need only basic HTML and JavaScript: a container, one SDK script, an API key, and a scene exported from Excelsior.
Before you start¶
You need a Viewer SDK API key whose allowlist includes your website, a .webex file exported with Export Interactive Scene, and a web server. Opening the HTML directly as file:///... is unsupported; for local work, run npx serve . in the page's directory. Use the http://localhost:... URL it prints, and ask us to include localhost in the API-key allowlist you use for development.
Host the scene beside the page while getting started. A scene on another origin needs that server's Access-Control-Allow-Origin header to permit your website.
Five-minute CDN setup¶
Create a folder containing index.html and ring.webex, then put this in index.html. Replace the API key and pinned SDK version with the values supplied to you.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Excelsior viewer</title>
<style>
html, body, #viewer { width: 100%; height: 100%; margin: 0; }
#viewer canvas { width: 100%; height: 100%; display: block; }
</style>
</head>
<body>
<div id="viewer"></div>
<script src="https://sdk.maverickexcelsior.com/webex-viewer-opengl/v2.0.0/webex-viewer.js"></script>
<script>
WebexViewer.mount({
container: '#viewer',
apiKey: 'ak_xxxxxxxxxxxxxxxx',
scene: new URL('./ring.webex', location.href).href,
background: 'white',
quality: 'high'
}).then(function (viewer) {
console.log('Scene ready', viewer);
}).catch(function (error) {
console.error(error.code, error.message);
});
</script>
</body>
</html>
Run npx serve ., open the URL it prints, and check the browser developer console if the scene does not appear. The usual causes are a wrong scene path, an API-key allowlist that does not include the page's domain, CORS restrictions, or mixed SDK versions.
Pin one immutable release
Always use a complete version path such as .../webex-viewer-opengl/v2.0.0/; there is no floating alias and no latest. The SDK, engine JavaScript, WASM, and data file must come from the same release.
Self-hosting¶
Copy these runtime files from one release into a directory and load its webex-viewer.js:
viewer/
├── webex-viewer.js
├── webex-viewer.mjs
├── webex-viewer-engine.js
├── webex-viewer-engine.wasm
└── webex-viewer-engine.data
<script src="./viewer/webex-viewer.js"></script>
The SDK finds the engine companions relative to its own URL. Keep their filenames and directory relationship intact. Serve .wasm as application/wasm, allow the SDK files to be fetched by the page, and allow cross-origin scene requests with CORS when the scene is hosted on another origin.
ES modules¶
Modern applications may import the module build. It creates no global variables.
<script type="module">
import { WebexViewer } from './viewer/webex-viewer.mjs';
const viewer = await WebexViewer.mount({
container: '#viewer',
apiKey: 'ak_xxxxxxxxxxxxxxxx',
scene: new URL('./ring.webex', location.href).href
});
</script>
Mount options¶
| Option | Meaning |
|---|---|
container | Element or selector into which the SDK creates a canvas. |
canvas | Existing canvas or selector; use this instead of container. |
apiKey | Viewer SDK API key. |
scene | Optional absolute scene URL to load before mount() resolves. |
baseUrl | Directory containing the engine files; normally auto-detected. |
engine | Alternate engine JavaScript filename or URL. |
background | Initial color, such as white or #f4f4f4. |
quality | medium, high, or ultra. |
autospin | Initial automatic rotation state; this is a startup option. |
infotag | Initial information-overlay state. |
renderAlphaMode | opaque or product-cutout. Internal builds only: the published engine accepts the option and ignores it. |
shortcode | Hosted-viewer shortcode, when applicable. |
loadTimeout | Scene-load timeout in milliseconds. |
contextAttributes | WebGL attributes, for example { alpha: true }. |
mount() resolves after the engine and optional scene are ready. It rejects with a WebexViewerError; error.code is the stable machine-readable reason.
If the canvas repeatedly dims or blinks, the page or scene host is not licensed by the key, the key is missing or invalid, or a scene was passed as a relative URL after activation. Check the exact page hostname, the scene URL's hostname, and the apiKey value with us. This activation indication is intentionally visible in the canvas and is not a substitute for handling JavaScript errors.
Main methods¶
await viewer.load(new URL('./another.webex', location.href).href);
viewer.close();
viewer.setMaterial('Metal 01', 'Yellow Gold 18k');
viewer.setProperty('::globals', 'globals_pose_id', '2');
const focal = await viewer.getProperty('::camera', 'tracker_cam_focal');
viewer.setQuality('ultra');
viewer.setBackground('#f0f0f0');
viewer.zoom('+1');
viewer.centerView();
const jpegDataUrl = await viewer.capture();
viewer.pushTrackingPose(packet); // High-frequency virtual try-on pose path.
viewer.destroy();
destroy() stops the runtime, rejects unfinished operations, removes an SDK-created canvas, and is safe to call more than once.
The API reference is the complete public method surface. Layer visibility is an ordinary property:
viewer.setProperty('Metal 01', 'obj_visible', '0');
Events¶
viewer.on('load-progress', ({ percentage }) => {
progress.textContent = Math.round(percentage) + '%';
});
viewer.once('scene-prepared', () => {
viewer.setProperty('Unselected Variant', 'obj_visible', 'false');
});
viewer.on('scene-ready', () => { controls.disabled = false; });
viewer.on('error', ({ error }) => console.error(error.code, error.message));
These examples register listeners for later viewer.load() calls. If scene is supplied to mount(), that initial scene is ready before the mount promise resolves. See the events reference for every stable event name, its payload, and subscription rules. Most integrations need scene-ready; scene-prepared is specifically for changing an opening scene before its completed presentation. Multipart methods report completion and failure through their returned promises.
Multipart scenes¶
The SDK accepts an absolute URL, Blob, ArrayBuffer, or typed-array view. Reusing a part ID atomically replaces that part. Convert page-relative paths with new URL() so the license check receives their hostname.
await viewer.load(new URL('./scenes/base.webex', location.href).href);
await viewer.mergePart(
'head',
new URL('./scenes/head-round.webex', location.href).href,
{ format: 'webex' }
);
await viewer.mergePart('engraving', glbBytes, { format: 'glb', filename: 'name.glb' });
await viewer.removePart('head');
await viewer.clearParts();
Loading the same URL as the immediately previous load is rejected after the load timeout with LOAD_TIMEOUT. If you need a fresh copy of that scene, destroy and mount a new viewer instance (or use a distinct versioned URL).
Working examples¶
The examples overview links to live, view-source-friendly integrations and explains what each one teaches, from a minimal mount to multipart generation.
Found a mistake?
If anything here is wrong or unclear, please contact us and we will fix it.