@3dsource/metabox-modular-configurator-api
v3.0.21
Published
API for Metabox MODULAR configurator
Readme
Metabox Modular Configurator API
TypeScript/JavaScript API for integrating Metabox Modular Configurator into web applications. Provides programmatic control over modular product configurations, component assembly, materials, environments, and export functionality.
Table of Contents
- Features
- Prerequisites
- Installation
- Quick Start
- API Reference
- Standalone Mode
- Building a Custom UI
- Troubleshooting
- Tips & Tricks
Features
- Framework-agnostic integration (Angular, React, Vue, vanilla JS)
- Modular component assembly and configuration
- Material and environment management per component
- Real-time configuration state synchronization
- Screenshot and PDF export capabilities
- E-commerce CTA workflow support
- Showcase/animation playback control
- Full TypeScript type definitions
- CDN support for rapid prototyping
- Event-driven architecture for reactive UI
Prerequisites
Runtime Requirements:
- Modern browser with ES6 module support (Chrome 61+, Firefox 60+, Safari 11+, Edge 79+)
- HTTPS connection (required by Unreal Engine streaming)
- Valid Metabox modular configurator ID
Development Environment:
- Node.js >= 20 (for NPM installation)
- npm > 9
Important Notes:
integrateMetabox()validates inputs at runtime- Requires non-empty
configuratorIdandcontainerId - Iframe URL format:
https://{domain}/metabox-configurator/modular/{configuratorId} - Optional query parameters:
introImage,introVideo,loadingImage - HTTPS is strictly enforced (HTTP URLs will be rejected)
- Throws error if container element not found in DOM
Installation
NPM Installation (Recommended)
Installation:
npm install @3dsource/metabox-modular-configurator-api@latest --saveImport:
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope, SetComponent, SetComponentMaterial, SetEnvironment, GetScreenshot, saveImage } from '@3dsource/metabox-modular-configurator-api';CDN Installation (Quick Prototyping)
jsDelivr:
import { integrateMetabox, SetComponent, SetEnvironment, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';Note: For production, pin a specific version instead of using
@latest
Quick Start
1. HTML Setup
Ensure your HTML page has the following structure, where the Metabox Configurator will be integrated.
You must provide CSS for the #embed3DSource element to make the configurator responsive and fit your layout needs.
Create a container element where the configurator will be embedded:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Metabox Configurator</title>
<style>
#embed3DSource {
width: 100%;
height: 600px;
border: 1px solid #ccc;
border-radius: 8px;
}
</style>
</head>
<body>
<div id="embed3DSource">
<!-- Configurator iframe will be embedded here -->
</div>
</body>
</html>2. Basic Integration
JavaScript (ES6 Modules)
<script type="module">
import { GetPdf, GetScreenshot, GetCallToActionInformation, integrateMetabox, saveImage, SetComponent, SetComponentMaterial, SetEnvironment, SetEnvironmentMaterial, ShowEmbeddedMenu, ShowOverlayInterface } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';
// Replace it with your actual configurator ID (not a full URL)
const configuratorId = 'configurator-id';
integrateMetabox(configuratorId, 'embed3DSource', (api) => {
console.log('Configurator API ready!');
// Listen for configurator data updates
api.addEventListener('configuratorDataUpdated', (data) => {
console.log('Configurator data:', data);
});
// Listen for screenshot events
api.addEventListener('screenshotReady', (data) => {
saveImage(data, 'configurator-screenshot.png');
});
// Initial commands example
api.sendCommandToMetabox(new SetComponent('componentId', 'typeId'));
api.sendCommandToMetabox(new SetEnvironment('environmentId'));
api.sendCommandToMetabox(new SetComponentMaterial('componentId', 'slotId', 'materialId'));
api.sendCommandToMetabox(new SetEnvironmentMaterial('slotId', 'materialId'));
api.sendCommandToMetabox(new ShowEmbeddedMenu(false));
api.sendCommandToMetabox(new ShowOverlayInterface(false));
api.sendCommandToMetabox(new GetPdf());
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1024, y: 1024 }));
api.sendCommandToMetabox(new GetCallToActionInformation());
});
</script>TypeScript/Angular/React
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope, SetEnvironment, ShowOverlayInterface, GetScreenshot, saveImage, SetComponent, SetComponentMaterial, type MimeType, ShowEmbeddedMenu } from '@3dsource/metabox-modular-configurator-api';
class ConfiguratorIntegration {
private api: Communicator | null = null;
private readonly configuratorId: string;
constructor(configuratorId: string) {
this.configuratorId = configuratorId;
this.initialize();
}
private initialize(): void {
integrateMetabox(this.configuratorId, 'embed3DSource', (api: Communicator) => {
this.api = api;
this.setupEventListeners();
this.setupInitialState();
});
}
private setupEventListeners(): void {
if (!this.api) {
return;
}
// Listen for configuration data changes
this.api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
console.log('Configurator data updated:', data);
this.handleConfiguratorData(data);
});
// Handle screenshot events
this.api.addEventListener('screenshotReady', (imageData: string | null) => {
saveImage(imageData ?? '', 'configurator-render.png');
});
// Handle viewport ready events
this.api.addEventListener('viewportReady', (isReady: boolean) => {
console.log('Viewport ready:', isReady);
});
// Handle status messages
this.api.addEventListener('statusMessageChanged', (message: string | null) => {
console.log('Status message:', message);
});
}
private setupInitialState(): void {
if (!this.api) return;
// Configure initial environment and components
this.api.sendCommandToMetabox(new SetEnvironment('default-environment'));
this.api.sendCommandToMetabox(new SetComponent('main-product', 'chair'));
this.api.sendCommandToMetabox(new ShowOverlayInterface(true));
this.api.sendCommandToMetabox(new ShowEmbeddedMenu(true));
}
private handleConfiguratorData(data: ModularConfiguratorEnvelope): void {
// Access typed data properties according to actual interface
console.log('Configurator:', data.configurator);
console.log('Configuration tree:', data.configurationTree);
console.log('Components by type:', data.componentsByComponentType);
console.log('Component materials:', data.componentMaterialsIds);
console.log('Environment ID:', data.environmentId);
console.log('Environment materials:', data.environmentMaterialsIds);
// Process configurator data according to your needs
this.updateUI(data);
}
private updateUI(data: ModularConfiguratorEnvelope): void {
// Update your application UI based on configurator state
// This method would contain your specific UI update logic
}
// Public API methods
public takeScreenshot(format: MimeType = 'image/png', size?: { x: number; y: number }): void {
if (this.api) {
this.api.sendCommandToMetabox(new GetScreenshot(format, size));
}
}
public changeMaterial(componentId: string, slotId: string, materialId: string): void {
if (this.api) {
this.api.sendCommandToMetabox(new SetComponentMaterial(componentId, slotId, materialId));
}
}
public changeEnvironment(environmentId: string): void {
if (this.api) {
this.api.sendCommandToMetabox(new SetEnvironment(environmentId));
}
}
public destroy(): void {
// Clean up resources when component is destroyed
this.api = null;
}
}
// Usage example
const configurator = new ConfiguratorIntegration('configurator-id');
// Take a high-resolution screenshot
configurator.takeScreenshot('image/png', { x: 1920, y: 1080 });
// Change material
configurator.changeMaterial('componentId', 'seat-fabric', 'leather-brown');API Reference
System Terminology
| Term | Description | Context | | --------------------- | -------------------------------------------------------------- | ---------------------- | | component | Modular product part/assembly (e.g., wheel, bumper, seat) | Modular configurator | | componentId | Unique component identifier (system-generated UUID) | Component management | | componentType | Category/classification of component (e.g., "wheel", "bumper") | Component organization | | environment | 3D scene/room environment for product visualization | Visual context | | environmentId | Unique environment identifier (system-generated UUID) | Environment management | | product | Base 3D digital twin referenced by components | Component definition | | productId | Unique product identifier (system-generated UUID) | Product reference | | externalId | Custom SKU/identifier for external system integration | E-commerce integration | | showcase | Camera animation sequence attached to component | Component attribute | | slotId | Material slot identifier on component/environment | Material assignment | | materialId | Unique material identifier | Material assignment | | configurationTree | Hierarchical structure of component assembly | State representation |
E-Commerce Configurator (CTA Integration):
E-Com configurators extend Modular configurators with a call-to-action button.
Configuration:
- CTA Button Label: Custom text (e.g., "Request Quote", "Add to Cart")
- Callback URL: HTTP POST endpoint for configuration data
CTA Workflow:
User clicks CTA → Metabox POSTs config JSON → Backend processes → Returns { redirectUrl } → User redirectedBackend Response Format:
{
"redirectUrl": "https://your.site/checkout-or-thank-you"
}Use Cases:
- Send lead to CRM with component details
- Redirect to custom checkout with modular pricing
Core Integration — integrateMetabox()
Embeds the Metabox Modular Configurator iframe and establishes communication channel.
Signature:
function integrateMetabox(configuratorId: string, containerId: string, apiReadyCallback: (api: Communicator) => void, config?: IntegrateMetaboxConfig): void;Parameters:
| Parameter | Type | Required | Default | Description |
| ------------------ | ------------------------ | -------- | ------- | ---------------------------------------- |
| configuratorId | string | ✅ | - | Modular configurator UUID (not full URL) |
| containerId | string | ✅ | - | DOM container element ID |
| apiReadyCallback | function | ✅ | - | Callback invoked when API is ready |
| config | IntegrateMetaboxConfig | ❌ | {} | Additional configuration options |
Config Options (IntegrateMetaboxConfig):
interface IntegrateMetaboxConfig {
standalone?: boolean; // Disable built-in Metabox UI
introImage?: string; // URL for intro image
introVideo?: string; // URL for intro video
loadingImage?: string; // URL for loading spinner
state?: string; // Initial component tree state (rison format)
domain?: string; // Override domain (HTTPS only)
}Validation & Errors:
- Throws if
configuratorIdis empty - Throws if a container element not found
- Throws if URL is not HTTPS
- Replaces existing iframe with ID
embeddedContent
Example:
integrateMetabox(
'modular-config-uuid',
'my-container',
(api) => {
console.log('Modular configurator ready');
// Work with API here, e.g., listen to events or send commands
},
{
standalone: true,
loadingImage: 'https://cdn.example.com/loader.gif',
},
);Type Definitions
The API provides comprehensive TypeScript support with the following key interfaces and types:
Core Types
Communicator
The main API interface for sending commands and listening to events.
class Communicator extends EventDispatcher {
sendCommandToMetabox<T extends CommandBase>(command: T): void;
addEventListener<T extends FromMetaBoxApiEvents>(messageType: T, callback: (data: FromMetaboxMessagePayloads[T]) => void): this;
removeEventListener<T extends FromMetaBoxApiEvents>(messageType: T, callback: (data: FromMetaboxMessagePayloads[T]) => void): this;
}ModularConfiguratorEnvelope
The main data structure contains the complete configurator state.
interface ModularConfiguratorEnvelope {
/** Represents the modular configurator api with its components, component types, and environments */
configurator: InitialModularConfigurator;
/** The unique tree of components and parent and component type id */
configurationTree: ComponentWithParent[];
/** A record mapping all components by current component type */
componentsByComponentType: Record<string, string>;
/** A record mapping component material selection */
componentMaterialsIds: SelectedIds;
/** The unique environment identifier */
environmentId: string;
/** A record mapping environment material selections */
environmentMaterialsIds: SelectedIds;
}ModularConfiguratorComponent
Represents a component in the configurator.
interface ModularConfiguratorComponent {
/** The id unique identifier. */
id: string;
/** The modular configurator component type identifier. */
componentType: ModularConfiguratorComponentType;
/** Display the name for the product. */
name: string;
/** Position index of the product. */
position: number;
/** The detailed product information. */
product: Product;
/** Connectors between connectors and virtual sockets. */
virtualSocketToConnectorsMap?: ModularConfiguratorVirtualSocketToConnector[];
/** GraphQL typename for the component. */
__typename: 'ModularConfiguratorComponent';
}Material
Represents a material that can be applied to components or environments.
interface Material {
/** Unique identifier for the material. */
id: string;
/** Title or name of the material. */
title: string;
/** Unique external id for the material. */
externalId: string;
/** List of thumbnails for the material. */
thumbnailList: Thumbnail[];
/** GraphQL typename for the material. */
__typename: 'Material';
}Environment
Represents an environment/scene configuration.
interface Environment {
/** Unique identifier for the environment. */
id: string;
/** Title or name of the environment. */
title: string;
/** Flag indicating if UDS is enabled. */
udsEnabled: boolean;
/** The north yaw of the sun in the environment. */
sunNorthYaw: number;
/** The UDS hour setting for the environment. */
udsHour: number;
/** List of thumbnails for the environment (optional). */
thumbnailList: Thumbnail[];
/** List of slots for the environment (optional). */
slots: Slot[];
/** GraphQL typename for the environment. */
__typename: 'Environment';
}InitialModularConfigurator
The full configurator definition containing all available components, environments, and component types.
interface InitialModularConfigurator {
/** Unique identifier for the configurator. */
id: string;
/** The name of the configurator. */
name: string;
/** A list of components for the configurator. */
components: ModularConfiguratorComponent[];
/** A list of available environments. */
environments: ModularConfiguratorEnvironment[];
/** A list of component types for the configurator. */
componentTypes: ModularConfiguratorComponentType[];
/** Indicates if the configurator is active. */
isActive: boolean;
/** Indicates if the CTA in ecom configurator is enabled. */
ctaEnabled: boolean;
/** A list of connectors showing connections between component types. */
connectors: ModularConfiguratorConnector[];
__typename: 'ModularConfigurator';
}ModularConfiguratorComponentType
Represents a component category/classification (e.g., "wheel", "bumper", "seat").
interface ModularConfiguratorComponentType {
/** Display name for the component type. */
name: string;
/** Position index of the component type. */
position: number;
/** Is root component type. */
isRoot: boolean;
/** Is required component type. */
isRequired: boolean;
/** Unique identifier. */
id: string;
__typename: 'ModularConfiguratorComponentType';
}Product
Base 3D digital twin referenced by each component.
interface Product {
/** The title of the product. */
title: string;
/** List of thumbnails for the product. */
thumbnailList: Thumbnail[];
/** Available material slots for the product. */
slots: Slot[];
/** List of meta properties for the product. */
metaProperties: MetaProperty[];
/** List of virtual sockets for modular assembly connections. */
virtualSockets: VirtualSocket[];
/** Unique identifier for the product. */
id: string;
/** External SKU/identifier for ERP/PIM integration. */
externalId: string;
/** Showcase animation details (optional). */
showcase?: Showcase;
__typename: 'Product';
}Slot
A material slot on a component or environment — a customizable surface.
interface Slot {
/** Unique identifier for the slot. */
id: string;
/** Display label for the slot. */
label: string;
/** Available materials for this slot. */
enabledMaterials: Material[];
__typename: 'Slot';
}ModularConfiguratorEnvironment
Environment wrapper containing position and label for environment selector UIs.
interface ModularConfiguratorEnvironment {
/** Unique identifier for the environment. */
environmentId: string;
/** Position index of the environment. */
position: number;
/** Display label for the environment. */
label: string;
/** Detailed environment configuration. */
environment: Environment;
__typename: 'ModularConfiguratorEnvironment';
}ComponentWithParent
Represents a node in the component assembly tree hierarchy.
interface ComponentWithParent {
id: string;
componentTypeId: string;
parentId: string | null;
}SelectedIds
Record type tracking currently selected material/component IDs.
type SelectedIds = Record<string, Record<string, string>>;ShowCaseStatus
Showcase animation playback status.
type ShowCaseStatus = 'init' | 'play' | 'pause' | 'stop';ModularConfiguratorConnector
Defines connection rules between component types for modular assembly.
interface ModularConfiguratorConnector {
id: string;
componentTypeId1: string;
componentTypeId2: string;
__typename: 'ModularConfiguratorConnector';
}Utility Types
MimeType
Supported image formats for screenshots.
type MimeType = 'image/png' | 'image/jpeg' | 'image/webp';Command Classes
Commands control configurator behavior. Send via api.sendCommandToMetabox(new CommandName(...)).
Component & Environment Management
| Command | Parameters | Description |
| ------------------------ | --------------------------------------------------------- | ---------------------------------- |
| SetComponent | componentId: string, typeId: string | Add/select component in assembly |
| SetComponentMaterial | componentId: string, slotId: string, materialId: string | Apply material to component slot |
| SetEnvironment | environmentId: string | Change environment/scene |
| SetEnvironmentMaterial | slotId: string, materialId: string | Apply material to environment slot |
Component Assembly Example:
// Set root component (e.g., vehicle chassis)
api.sendCommandToMetabox(new SetComponent('chassis-001', 'base'));
// Add child component (e.g., wheel to chassis)
api.sendCommandToMetabox(new SetComponent('wheel-fr', 'wheel'));
// Apply materials
api.sendCommandToMetabox(new SetComponentMaterial('chassis-001', 'body', 'metallic-blue'));
api.sendCommandToMetabox(new SetComponentMaterial('wheel-fr', 'rim', 'chrome'));UI Control
- Note: ShowEmbeddedMenu and ShowOverlayInterface commands are ineffective in standalone mode.
| Command | Parameters | Description |
| ---------------------- | ------------------ | ---------------------------- |
| ShowEmbeddedMenu | visible: boolean | Show/hide right sidebar menu |
| ShowOverlayInterface | visible: boolean | Show/hide viewport controls |
Camera Control
| Command | Parameters | Description |
| ------------- | ------------------------------ | ----------------------------------------------------------- |
| SetCamera | camera: CameraCommandPayload | Set camera position, rotation, fov and restrictions |
| GetCamera | None | Request current camera; returns via getCameraResult event |
| ResetCamera | None | Reset camera to default position |
| ApplyZoom | zoom: number | Apply zoom delta (positive=in, negative=out) |
Example:
api.sendCommandToMetabox(new ApplyZoom(50)); // Zoom in
api.sendCommandToMetabox(new ApplyZoom(-25)); // Zoom out
api.sendCommandToMetabox(new ResetCamera()); // Reset view
api.sendCommandToMetabox(
new SetCamera({
fov: 45,
mode: 'orbit',
position: { x: 100, y: 100, z: 100 },
rotation: { horizontal: 0, vertical: 0 },
restrictions: {
maxDistanceToPivot: 0,
maxFov: 0,
maxHorizontalRotation: 0,
maxVerticalRotation: 0,
minDistanceToPivot: 0,
minFov: 0,
minHorizontalRotation: 0,
minVerticalRotation: 0,
},
}),
);
// Request current camera state
api.sendCommandToMetabox(new GetCamera());
// Listen for the camera data
api.addEventListener('getCameraResult', (camera) => {
console.log('Current camera:', camera);
// camera is of type CameraCommandPayload
});CameraCommandPayload
The payload used by the SetCamera command to configure the camera.
interface CameraCommandPayload {
fov?: number;
mode?: 'fps' | 'orbit';
position?: { x: number; y: number; z: number };
rotation?: {
horizontal: number;
vertical: number;
};
restrictions?: {
maxDistanceToPivot?: number;
maxFov?: number;
maxHorizontalRotation?: number;
maxVerticalRotation?: number;
minDistanceToPivot?: number;
minFov?: number;
minHorizontalRotation?: number;
minVerticalRotation?: number;
};
}fov(number): Field of view in degrees.mode('fps' | 'orbit'):fps: First‑person style camera, moves freely in space.orbit: Orbiting camera around a pivot (typical product viewer behavior).
position({ x, y, z }): Camera position in world coordinates.rotation({ horizontal, vertical }): Camera rotation angles in degrees.horizontal: Yaw (left/right).vertical: Pitch (up/down).
restrictions(optional): Limits applied by the viewer to constrain user/camera movement.- Distance limits (orbit mode):
minDistanceToPivot,maxDistanceToPivot. - Field‑of‑view limits:
minFov,maxFov. - Rotation limits:
minHorizontalRotation,maxHorizontalRotation,minVerticalRotation,maxVerticalRotation.
- Distance limits (orbit mode):
Notes:
- Provide only the limits you want to enforce; unspecified values are left as currently configured by the viewer.
- Rotation units are degrees; positive/negative values follow the viewer's right‑handed coordinate system.
- In
orbitmode, distance limits are interpreted relative to the orbit pivot. ResetCamerarestores the default position/rotation/FOV defined by the current product or environment template.
Showcase/Animation Control
| Command | Parameters | Description |
| --------------- | ---------- | ----------------------------------------- |
| InitShowcase | None | Initialize showcase for current component |
| PlayShowcase | None | Start/resume animation playback |
| PauseShowcase | None | Pause animation |
| StopShowcase | None | Stop and reset animation |
Measurement Tools
| Command | Parameters | Description |
| ----------------- | ---------- | ---------------------------- |
| ShowMeasurement | None | Display component dimensions |
| HideMeasurement | None | Hide dimension overlay |
Stream Control
| Command | Parameters | Description |
| -------------- | ---------- | ----------------------------------------------------------- |
| ResumeStream | None | Resume pixel streaming session after pause or disconnection |
Example:
// Resume the stream after an idle timeout or network interruption
api.sendCommandToMetabox(new ResumeStream());Export & Media
| Command | Parameters | Description | Event Triggered |
| ---------------------------- | ------------------------------------------------- | -------------------- | ---------------------- |
| GetScreenshot | format: MimeType, size?: {x: number, y: number} | Render screenshot | screenshotReady |
| GetPdf | None | Generate PDF export | Server-side (no event) |
| GetCallToActionInformation | None | Trigger CTA workflow | Backend redirect |
saveImage()
Helper function: triggers a browser download of an image from a data URL or blob URL.
Signature:
function saveImage(imageUrl: string, filename: string): void;| Parameter | Type | Description |
| ---------- | -------- | ----------------------------------- |
| imageUrl | string | Base64 data URL or blob URL to save |
| filename | string | Suggested filename for the download |
Screenshot Example:
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 2048, y: 2048 }));
api.addEventListener('screenshotReady', (imageData) => {
saveImage(imageData, 'assembly.png');
});Event Handling
Event-driven architecture for reactive state management. Register listeners via api.addEventListener(eventName, handler).
Available Events
| Event | Payload Type | Description | Use Case |
| ----------------------------- | --------------------------------------------- | ---------------------------- | ------------------------------------- |
| configuratorDataUpdated | ModularConfiguratorEnvelope | Configuration state changed | Sync UI with component tree/materials |
| ecomConfiguratorDataUpdated | EcomConfigurator | CTA configuration loaded | Display CTA button with label |
| viewportReady | boolean | 3D viewport ready state | Hide loading, enable interactions |
| showcaseStatusChanged | ShowCaseStatus | Animation playback status | Update play/pause button state |
| statusMessageChanged | string \| null | Loading/progress message | Display user feedback |
| screenshotReady | string \| null | Base64 screenshot data | Download or display image |
| getCameraResult | CameraCommandPayload | Current camera data returned | Capture/store current camera |
| videoResolutionChanged | {width: number\|null, height: number\|null} | Stream resolution changed | Adjust viewport layout |
Event Examples
Configuration State Sync:
api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
console.log('Environment:', data.environmentId);
console.log('Component tree:', data.configurationTree);
console.log('Components by type:', data.componentsByComponentType);
console.log('Component materials:', data.componentMaterialsIds);
// Update custom UI
updateComponentTree(data.configurationTree);
updateMaterialSelectors(data.componentMaterialsIds);
});Screenshot Handling:
api.addEventListener('screenshotReady', (imageData: string | null) => {
if (!imageData) {
console.error('Screenshot failed');
return;
}
saveImage(imageData, `assembly-${Date.now()}.png`);
});Loading State:
api.addEventListener('viewportReady', (isReady: boolean) => {
if (isReady) {
hideLoadingSpinner();
enableComponentSelectors();
}
});
api.addEventListener('statusMessageChanged', (message: string | null) => {
document.getElementById('status-text').textContent = message || '';
});Showcase Control:
api.addEventListener('showcaseStatusChanged', (status: ShowCaseStatus) => {
const playButton = document.getElementById('play-btn');
switch (status) {
case 'play':
playButton.textContent = 'Pause';
break;
case 'pause':
case 'stop':
playButton.textContent = 'Play';
break;
}
});Utility Functions
saveImage(imageUrl: string, filename: string): void
Downloads base64-encoded image to user's device.
Parameters:
| Parameter | Type | Description |
| ---------- | -------- | --------------------------------------------------- |
| imageUrl | string | Base64 data URL (e.g., data:image/png;base64,...) |
| filename | string | Desired filename with extension |
Example:
api.addEventListener('screenshotReady', (imageData: string | null) => {
if (imageData) {
saveImage(imageData, `config-${Date.now()}.png`);
}
});Implementation Note:
Creates a temporary anchor element with download attribute to trigger browser download.
fromCommunicatorEvent(target: Communicator, eventName: string): Observable
RxJS wrapper for Communicator events. Works like RxJS fromEvent — creates a typed Observable that emits each time the specified event fires. Alternative to api.addEventListener().
Parameters:
| Parameter | Type | Description |
| ----------- | -------------- | -------------------------------------------- |
| target | Communicator | The Communicator instance to listen on |
| eventName | string | Event name (e.g., configuratorDataUpdated) |
Returns: Observable<T> — typed Observable matching the event payload.
Example:
import { fromCommunicatorEvent } from '@3dsource/metabox-modular-configurator-api';
integrateMetabox('configurator-id', 'embed3DSource', (api) => {
fromCommunicatorEvent(api, 'configuratorDataUpdated').subscribe((data) => {
console.log('Components:', data.componentsByComponentType);
});
fromCommunicatorEvent(api, 'screenshotReady').subscribe((imageData) => {
if (imageData) saveImage(imageData, 'screenshot.png');
});
});Standalone Mode
Headless rendering mode for custom UI implementations. Disables all built-in Metabox UI elements. Pass { standalone: true } as the config option to integrateMetabox().
When to Use Standalone Mode
✅ Use When:
- Building fully custom modular configurator interface
- Implementing brand-specific component selection UX
- Integrating into existing design systems
- Requiring complete control over component assembly flow
❌ Don't Use When:
- Quick prototyping with default UI is sufficient
- Minimal customization needed
- Limited development resources
Behavior Changes
| Feature | Default Mode | Standalone Mode | | ------------------------- | ------------ | --------------- | | Right sidebar menu | ✅ Visible | ❌ Hidden | | Viewport overlays | ✅ Visible | ❌ Hidden | | Template logic | ✅ Active | ❌ Disabled | | API control | ⚠️ Partial | ✅ Full | | Event system | ✅ Available | ✅ Available | | Component tree management | ⚠️ Shared | ✅ Full control |
Implementation
TypeScript:
import { integrateMetabox, Communicator } from '@3dsource/metabox-modular-configurator-api';
integrateMetabox(
'modular-config-id',
'embed3DSource',
(api: Communicator) => {
// Work with API here
},
{ standalone: true }, // <- Enable standalone mode to disable default UI
);JavaScript (CDN):
<script type="module">
import { integrateMetabox } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-modular-configurator-api@latest/+esm';
integrateMetabox(
'config-id',
'container-id',
(api) => {
// Work with API here
},
{ standalone: true }, // <- Enable standalone mode to disable default UI
);
</script>Building a Custom UI
To create a custom modular configurator interface, use standalone mode and subscribe to events before sending commands.
Important: The components menu approach below is used only for building a custom components menu. For building a custom environments menu, iterate configurator.environments directly and use environmentMaterialsIds to show active materials by slots.
Core Pattern:
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope } from '@3dsource/metabox-modular-configurator-api';
integrateMetabox(
'configurator-uuid',
'containerId',
(api: Communicator) => {
// 1. Subscribe to events BEFORE sending commands
api.addEventListener('configuratorDataUpdated', (data: ModularConfiguratorEnvelope) => {
// data.configurator.componentTypes — available component types
// data.configurator.connectors — available connectors
// data.configurator.environments — scene list
// data.configurationTree — ordered tree of active components
});
api.addEventListener('viewportReady', (ready) => {
/* gate UI on this */
});
// 2. Send commands via api.sendCommandToMetabox(new CommandClass(...args))
},
{ standalone: true },
);Building a Custom Menu (Preferred Approach)
The menu must follow the configurationTree order — NOT iterate componentTypes or components as separate flat lists.
Data Sources from ModularConfiguratorEnvelope:
| Field | Type | Purpose |
| --------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
| configurationTree | ComponentWithParent[] | Ordered tree of active component nodes (id, componentTypeId, parentId) |
| componentsByComponentType | Record<string, string> | Maps componentTypeId → active componentId |
| componentMaterialsIds | SelectedIds (Record<string, Record<string, string>>) | Maps componentId → { slotId: materialId } |
| configurator | InitialModularConfigurator | Catalog: all componentTypes, components, environments, connectors |
Algorithm:
Iterate configurationTree nodes. For each node:
- Find the component type —
configurator.componentTypes.find(t => t.id === node.componentTypeId) - Render a component selector — show all components of that type (from
configurator.componentsfiltered bycomponentType.id). The active component iscomponentsByComponentType[typeId]. - Render material slots — for the active component, show its
product.slots(filtered to those withenabledMaterials.length > 0). The selected material for each slot comes fromcomponentMaterialsIds[componentId][slotId].
This produces a single ordered list interleaved as: Type1 selector → Type1 material slots → Type2 selector → Type2 material slots → …
Example (Angular Signals):
import { computed, signal } from '@angular/core';
import type { Communicator, ModularConfiguratorEnvelope, ModularConfiguratorComponent, Slot } from '@3dsource/metabox-modular-configurator-api';
import { fromCommunicatorEvent, SetComponent, SetComponentMaterial } from '@3dsource/metabox-modular-configurator-api';
// Store envelope data as a signal (updated on each configuratorDataUpdated event)
const envelope = signal<ModularConfiguratorEnvelope | null>(null);
// Subscribe to API events
function listenApi(api: Communicator) {
fromCommunicatorEvent(api, 'configuratorDataUpdated').subscribe((data) => {
envelope.set(data);
});
}
// Build menu tree as a computed signal following configurationTree order
const menuTree = computed(() => {
const data = envelope();
if (!data) return [];
const { configurator, configurationTree, componentsByComponentType, componentMaterialsIds } = data;
const menuItems: MenuEntry[] = [];
for (const node of configurationTree) {
const ct = configurator.componentTypes.find((t) => t.id === node.componentTypeId);
if (!ct) continue;
const components = configurator.components.filter((c) => c.componentType.id === ct.id).sort((a, b) => a.position - b.position);
const activeComponentId = componentsByComponentType[ct.id];
const activeComponent = components.find((c) => c.id === activeComponentId);
// 1. Component type selector
menuItems.push({
id: ct.id,
category: ct.name,
options: components.map((item) => ({
label: item.name,
id: item.id,
image: item.product.thumbnailList[0]?.urlPath,
action: () => api.sendCommandToMetabox(new SetComponent(item.id, ct.id)),
})),
selectedId: activeComponentId,
selectedLabel: activeComponent?.name,
});
// 2. Material slots for the active component
if (activeComponent) {
for (const slot of activeComponent.product.slots) {
if (slot.enabledMaterials.length === 0) continue;
const selectedMaterialId = componentMaterialsIds?.[activeComponent.id]?.[slot.id];
menuItems.push({
id: `${activeComponent.id}:${slot.id}`,
category: slot.label,
options: slot.enabledMaterials.map((mat) => ({
label: mat.title,
id: mat.id,
image: mat.thumbnailList[0]?.urlPath,
action: () => api.sendCommandToMetabox(new SetComponentMaterial(activeComponent.id, slot.id, mat.id)),
})),
selectedId: selectedMaterialId,
selectedLabel: slot.enabledMaterials.find((m) => m.id === selectedMaterialId)?.title,
});
}
}
}
return menuItems;
});Commands for Selection:
| Action | Command | Args |
| ---------------- | ------------------------------------------------------- | ----------------------------------------------- |
| Select component | SetComponent(id, typeId) | id: component ID, typeId: component type ID |
| Select material | SetComponentMaterial(componentId, slotId, materialId) | all three IDs required |
Anti-Patterns:
- DON'T build two separate lists (component types + material slots) — this loses tree ordering
- DON'T iterate
configurator.componentTypesdirectly — useconfigurationTreeto determine which types are active and their order - DON'T show all component types — only those present as keys in
componentsByComponentType(i.e., matchingconfigurationTreenodes)
Export Configuration
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
api.sendCommandToMetabox(new GetPdf());
api.sendCommandToMetabox(new GetCallToActionInformation());Troubleshooting
Common Issues
1. Configurator Not Loading
Symptoms: Blank screen, iframe not appearing, loading indefinitely
Root Causes & Solutions:
| Issue | Diagnostic | Solution |
| --------------------------- | ------------------------------------ | --------------------------------------------------------- |
| Invalid configurator ID | Browser console shows 404 errors | Verify modular configurator ID in Metabox admin |
| Container not found | Error: Container element not found | Ensure element exists before calling integrateMetabox() |
| HTTP instead of HTTPS | Mixed content warnings | Use HTTPS or test on localhost |
| Container has no dimensions | Invisible iframe (0x0) | Set explicit width/height on container element |
| CORS/iframe blocking | X-Frame-Options errors | Check domain allowlist in Metabox settings |
Diagnostic Code:
const containerId = 'embed3DSource';
const container = document.getElementById(containerId);
if (!container) {
throw new Error(`Container #${containerId} not found`);
}
// Verify container has dimensions
const rect = container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
console.warn('⚠️ Container has no dimensions');
}
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.warn('⚠️ HTTPS required for Unreal Engine streaming');
}
integrateMetabox('config-id', containerId, (api) => {
console.log('✅ Modular configurator loaded');
});2. Commands Not Working
Symptoms: Commands sent but no visual changes in configurator
Common Mistakes:
| Mistake | Problem | Solution |
| ------------------------ | ------------------- | ----------------------------------------------- |
| Sending before API ready | Commands ignored | Only send in apiReadyCallback |
| Wrong component IDs | Component not found | Verify IDs from configuratorDataUpdated event |
| Type mismatch | Component rejected | Ensure typeId matches available types |
Correct Pattern:
integrateMetabox('config-id', 'container', (api) => {
// ✅ Correct: Commands sent after API ready
// First: Set root component
api.sendCommandToMetabox(new SetComponent('comp-1', 'chassis'));
// Then: Add child components
api.sendCommandToMetabox(new SetComponent('comp-2', 'wheel'));
// Finally: Apply materials
api.sendCommandToMetabox(new SetComponentMaterial('comp-1', 'body', 'blue'));
});
// ❌ Wrong: Command sent too early
api.sendCommandToMetabox(new SetComponent('comp-1', 'chassis'));Getting Help
If you're still experiencing issues:
- Check the browser console for error messages
- Verify your configurator ID in a separate browser tab
- Test with a minimal example to isolate the issue
- Contact support with: browser version, configurator URL, console errors, and steps to reproduce
Tips & Tricks
- Debounce rapid commands. Avoid sending too many commands in quick succession — debounce material or component changes (e.g., 300 ms).
- Responsive container. Set the configurator container to responsive dimensions (
width: 100%; height: 100%;) and adjust with media queries for mobile. - Events work in all modes.
configuratorDataUpdated,screenshotReady, and other events fire in both default and standalone mode — use them to keep your UI synchronized. - Wait for API ready. Only send commands inside the
apiReadyCallback. Commands sent before initialization are silently ignored. - Validate IDs from events. Use the
configuratorDataUpdatedevent payload to discover valid component, material, and slot IDs rather than hardcoding them. - HTTPS is required. The iframe URL enforces HTTPS. HTTP URLs will be rejected. Use
localhostfor local development. - Pin CDN versions in production. Replace
@latestwith a specific version to avoid unexpected breaking changes.
For more examples and advanced usage, visit our documentation site.
