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.
Runtime Requirements:
Development Environment:
Important Notes:
integrateMetabox() validates inputs at runtimeconfiguratorId and containerIdhttps://{domain}/metabox-configurator/modular/{configuratorId}introImage, introVideo, loadingImageInstallation:
npm install @3dsource/metabox-modular-configurator-api@latest --save
Import:
import { integrateMetabox, Communicator, ModularConfiguratorEnvelope, SetComponent, SetComponentMaterial, SetEnvironment, GetScreenshot, saveImage } from '@3dsource/metabox-modular-configurator-api';
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
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>
<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>
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');
| 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 Workflow:
User clicks CTA → Metabox POSTs config JSON → Backend processes → Returns { redirectUrl } → User redirected
Backend Response Format:
{
"redirectUrl": "https://your.site/checkout-or-thank-you"
}
Use Cases:
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:
configuratorId is emptyembeddedContentExample:
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',
},
);
The API provides comprehensive TypeScript support with the following key interfaces and types:
CommunicatorThe 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;
}
ModularConfiguratorEnvelopeThe 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;
}
ModularConfiguratorComponentRepresents 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';
}
MaterialRepresents 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';
}
EnvironmentRepresents 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';
}
InitialModularConfiguratorThe 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';
}
ModularConfiguratorComponentTypeRepresents 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';
}
ProductBase 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';
}
SlotA 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';
}
ModularConfiguratorEnvironmentEnvironment 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';
}
ComponentWithParentRepresents a node in the component assembly tree hierarchy.
interface ComponentWithParent {
id: string;
componentTypeId: string;
parentId: string | null;
}
SelectedIdsRecord type tracking currently selected material/component IDs.
type SelectedIds = Record<string, Record<string, string>>;
ShowCaseStatusShowcase animation playback status.
type ShowCaseStatus = 'init' | 'play' | 'pause' | 'stop';
ModularConfiguratorConnectorDefines connection rules between component types for modular assembly.
interface ModularConfiguratorConnector {
id: string;
componentTypeId1: string;
componentTypeId2: string;
__typename: 'ModularConfiguratorConnector';
}
MimeTypeSupported image formats for screenshots.
type MimeType = 'image/png' | 'image/jpeg' | 'image/webp';
Commands control configurator behavior. Send via api.sendCommandToMetabox(new CommandName(...)).
| 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'));
| Command | Parameters | Description |
|---|---|---|
ShowEmbeddedMenu |
visible: boolean |
Show/hide right sidebar menu |
ShowOverlayInterface |
visible: boolean |
Show/hide viewport controls |
| 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
});
CameraCommandPayloadThe 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.
minDistanceToPivot, maxDistanceToPivot.minFov, maxFov.minHorizontalRotation, maxHorizontalRotation, minVerticalRotation, maxVerticalRotation.Notes:
orbit mode, distance limits are interpreted relative to the orbit pivot.ResetCamera restores the default position/rotation/FOV defined by the current product or environment template.| 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 |
| Command | Parameters | Description |
|---|---|---|
ShowMeasurement |
None | Display component dimensions |
HideMeasurement |
None | Hide dimension overlay |
| 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());
| 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-driven architecture for reactive state management. Register listeners via api.addEventListener(eventName, handler).
| 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 |
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;
}
});
saveImage(imageUrl: string, filename: string): voidDownloads 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): ObservableRxJS 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');
});
});
Headless rendering mode for custom UI implementations. Disables all built-in Metabox UI elements. Pass { standalone: true } as the config option to integrateMetabox().
✅ Use When:
❌ Don't Use When:
| 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 |
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>
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 },
);
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:
configurator.componentTypes.find(t => t.id === node.componentTypeId)configurator.components filtered by componentType.id). The active component is componentsByComponentType[typeId].product.slots (filtered to those with enabledMaterials.length > 0). The selected material for each slot comes from componentMaterialsIds[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:
configurator.componentTypes directly — use configurationTree to determine which types are active and their ordercomponentsByComponentType (i.e., matching configurationTree nodes)api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
api.sendCommandToMetabox(new GetPdf());
api.sendCommandToMetabox(new GetCallToActionInformation());
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');
});
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'));
If you're still experiencing issues:
width: 100%; height: 100%;) and adjust with media queries for mobile.configuratorDataUpdated, screenshotReady, and other events fire in both default and standalone mode — use them to keep your UI synchronized.apiReadyCallback. Commands sent before initialization are silently ignored.configuratorDataUpdated event payload to discover valid component, material, and slot IDs rather than hardcoding them.localhost for local development.@latest with a specific version to avoid unexpected breaking changes.For more examples and advanced usage, visit our documentation site.