# Multi-object Configurator

The Multi-object Configurator allows you to configure not only one object but several objects in relation to each other.

# Enable multi-object configuration

To enable MoC:

  • To enable multi-object configuration, you need to add and set the moc flag in the Rubens settings to true (see Rubens Admin documentation (opens new window)). When this flag is set, all configurators in the tenant are automatically multi-object configurators.

# Setup the catalog structure

To offer a set of products in the MoC, they must be stored in a catalog and tagged accordingly. To display products and divide them into categories, tags must be structured correctly.

Each tag can be used as root tag for the MoC. A sub-tag must be created for each category and each product that corresponds to this category must be tagged with it. The tag structure could then look like this:

1 moc_catalog (root-tag)
2  |
3  ├── Fences (sub-tag 1)
4  |
5  ├── Couch Tables (sub-tag 2)
6  |
7  ├── Outdoor Tables (sub-tag 3)
8  |
9  └── Sofas (sub-tag 4)

Also see the documentation on tags

By tagging the products in the catalog, you can add them to the scene by clicking the "Add Product" button.

NOTE: In order to use the tags in the MOC, the catalog that hosts these tags should be Free and Available for public. You can set these flags inside the catalog detail page in the Admin interface. More info can be found here documentation on create catalog

# Define custom materials

You can customize which materials are used to color:

  • walls
  • floors
  • colorable doors, windows and objects (be aware that not every door or window or object is colorable)

First of all you have to create a root tag for the materials you want to use in Rubens Admin. The tag structure is exactly the same as for the catalog. For more details see the section above (setup catalog). The only difference is, that you tag materials instead of items. Another important difference is, that additionally to the visibility rights describted in the catalog section, the materials need to belong to the tenant that instantiates the Rubens Room Designer. Normally that's not a problem when you setup the materials in Rubens Admin inside your tenant. But make sure that you use the correct configurator ID. Otherwise the materials won't be visible.

If you have setup the tag, then you just need to set the options correctly (either via query params, configurator settings or init-options).

You can set different tags for the different things you want to color:

  • walls: wallMaterialRootTag
  • floors: floorMaterialRootTag
  • doors: doorMaterialRootTag
  • windows: windowMaterialRootTag
  • objects: objectMaterialRootTag

If you do not want to use a separate tag for everything you can just use materialRootTag and this is applied to everything. If no tag is specified Rubens Room Designer falls back to standard RAL colors. If you want to use a specific materialRootTag for everything but for the walls you want to use the RAL colors, you would specify it the following way:

  • materialRootTag=my_custom_material_root_tag_id&wallMaterialRootTag=__SAMPLE__MATERIALS__

The phrase __SAMPLE__MATERIALS__ indicates to use the fallback materials.

# Integration

To embed or integrate the MoC, the following parameters must be included in the URL

  • configuratorId: The configurator ID of the tenant where you enabled the MoC
  • catalogRootTag: The tag of the catalog where you stored the products you want to show in the MoC, Alternatively you could also set the root-tag ID as catalogRootTag in your Rubens settings.
  • moc: Enable the MoC feature in the Rubens settings or via integration.

Also see integration documentation: https://docs.roomle.com/web/embedding/integration.html (opens new window)

# Embedding:

If you are using the embedding library you have to call createPlanner instead of create or createConfigurator to load a MoC scene.

# Start scenarios for embedding the configurator into your website

Overall there are 2 possibilities to start with:

You can pass the item id or plan id using the id parameter.

# Relevant parameters

startInDetail Documentation (opens new window)

state.mode Documentation (opens new window)

# Listen to onSaveDraft

To be informed when an action to save the draft is initiated, whether by the user inside the iframe or triggered externally, you should utilize the onSaveDraft callback.

const planner = await RoomleConfiguratorApi.createPlanner(
planner.ui.callbacks.onSaveDraft = (id, image, url, { type, payload }) => {
  console.log(id, image, url, type, payload);

There are times when you wish to initiate the save draft process externally for example is we use elements.bottom_bar=false to use custom bottomBar or use custom saveDraft button. For such cases, we provide the method triggerSaveDraft

const planner = await RoomleConfiguratorApi.createPlanner(
planner.ui.callbacks.onSaveDraft = (id, image, url, { type, payload }) => {
  console.log(id, image, url, type, payload);
document.getElementById('trigger-save').addEventListener('click', async () => {
  await planner.ui.triggerSaveDraft('user@example.com');

onSaveDraft parameters:

id: A unique string identifier for the configuration Id or the plan Id. image: The image of the current configuration. url: The generated url from save draft. data: An object containing: type: It specifies the kind of draft. It can be either plan if you trigger save from the planner or configuration of you trigger save from the configurator. payload: The actual saved data, which could be the configuration object or plan snapshot data.

When you use the triggerSaveDraft method, it saves the current configuration and then activates the onSaveDraft callback after triggerSaveDraft is finished. If you want to monitor and respond to actions from the custom save draft button, you can set up the onSaveDraft callback and execute your custom code inside it. The returned data from this callback aligns with the saved data in Roomle's backend, facilitating functionalities like reloading the configuration based on the draft ID later on. This comes in handy when users wish to return to a previous configuration, be it after a short break, the following day, or even post browser restarts.

Using onSaveDraft, there is also discern patterns or statistics such as how many users who started a configuration opted to save their drafts. This metric offers insights into user behaviors, shedding light on potential improvements or user tendencies. Given the importance of this callback, we strongly advise incorporating it seamlessly into your

You can try and play around with this example her: Edit intelligent-merkle-s589m (opens new window)

# Recipes

# Adding items to the scene

It’s possible to add additional products/items/configurations to the scene using the insertObject API:

await interface.ui.insertObject('usm:frame');

By default the object is placed somewhere where it does not overlap with other items in the scene. You can also supply a position and a rotation for the object in the scene:

await interface.ui.insertObject('usm:frame', { x: -3, y: 0, z: 3 }, Math.PI);

Position (2nd parameter) is in meters and the rotation (3rd parameter) is in radians.

# Gets called when something is changed in the plan

It is possible to implement a behavior which gets called when there is something changed in the plan. It gets all objects of the plan as parameter.

interface.ui.callbacks.onPlanUpdate = (objects) => doSomething;

# React on specific plan element changes

You can react to certain plan element changes using the onPlanElementChanged(changeType) callback.

Depending on what happened, the changeType parameter can be:

  • added: When a new plan element is added to the scene
  • removed: When an existing plan element gets removed
  • changed: When a plan element is changed, like rotated or moved. But also when it is added or removed
  • updated: When the configuration of a configurable product inside the scene is updated.

You can try and play around with this example here: Edit intelligent-merkle-s589m (opens new window)

# React when tooltip is closed by controls button

You can react on any time when a tooltip is closed on controls button click As a parameter you get the name of the tooltip, e.g. "stopConfiguring"

interface.ui.callbacks.onTooltipClose = (tooltipName) => doSomething;

# Add products from external catalog

To integrate an external catalog into Rubens Room Designer and add products seamlessly to your design scenes, follow these steps:

  1. Attach Events to Product Cards: Before you can make products interact from the external catalog to the Room Designer, ensure that you attached the proper events to it like insertProduct event, See the sample script below.

  2. Provide Event Data: you must adhere to the following API structure. This structure specifies the format for the message object that will be sent to the Room Designer, including the eventType and payload fields, which includes the productId.

export interface MessageEventData {
  eventData: {
    eventType: 'insertProduct';
    payload: {
      productId: string;
  1. Ensure CORS Compatibility: Make sure that the external catalog is embeddable. For more information see CORS Integration Guide

# Integration Example

You can use the following code as a reference for implementing interaction features with the Room Designer. This script adds necessary actions based on the data-product-id attribute to handle product addition to the Room Designer scene. Customize this script to meet your specific requirements:

      const EVENTS_MAP = {
        CLICK: { eventName: "insertProduct", nativeEventName: "click" }

      const sendEvent = (eventType, productId) => {
        const parentWindow = window.parent;
        if (parentWindow) {
          const eventData = {
            payload: {
          parentWindow.postMessage({ eventData }, "*");

      const addEventHandlers = ({ eventName, nativeEventName }) => {
        const elementsWithEvent = document.querySelectorAll(
        elementsWithEvent.forEach((element) => {
          const productId = element.getAttribute("data-product-id");
          const eventHandler = (event) => {
            sendEvent(eventName, productId);

          element.addEventListener(nativeEventName, eventHandler);

      const removeEventHandlers = ({ nativeEventName }) => {
        const elementsWithEvent = document.querySelectorAll(
        elementsWithEvent.forEach((element) => {
          if (element.eventHandler) {
            element.removeEventListener(nativeEventName, element.eventHandler);
            delete element.eventHandler;

      const addClickHandlers = () => {

      const removeEventListeners = () => {

      window.addEventListener("load", () => {

      window.addEventListener("beforeunload", removeEventListeners);

# CORS Integration Guide

CORS (Cross-Origin Resource Sharing) is a security feature implemented by web browsers. It controls which web pages are allowed to make requests to different domains. CORS prevents malicious websites from making unauthorized requests.

# Understanding CORS Errors

When a CORS issue occurs, the browser typically returns one of the following error messages:

  1. CORS Request Blocked: The browser blocks the request because it violates the same-origin policy.

  2. No 'Access-Control-Allow-Origin' Header: The server does not include the required CORS headers in its response.

  3. Preflight Request Did Not Succeed: The browser sends a preflight OPTIONS request, and it does not receive a successful response from the server.

  4. Request Blocked Due to Preflight Response: The server's response to the preflight OPTIONS request indicates that the actual request is not allowed.

# How to Identify a CORS Issue

You may encounter a CORS issue if:

  • You see a CORS-related error message in the browser's developer console.
  • Your AJAX or fetch requests to an external domain are being blocked.
  • You notice that the content from the external domain is not loading or is displaying incorrectly on your web page.

# Dealing with CORS Issues

There are several solutions to address CORS issues:

  1. Server-Side Proxy: Consider setting up a server-side proxy that fetches content from your server and serves it to our app. This way, our app makes requests to the same domain (the proxy server), avoiding CORS issues.

  2. CORS Headers: Configure your server to include the necessary CORS headers in its responses. The key header is Access-Control-Allow-Origin, which specifies which domains are allowed to access your resources.

# Configuration Options for Different Scenarios

The approach you choose depends on your specific integration scenario:

  • If You Control the Server: Modify your server's CORS configuration to allow requests from our app's domain. Consult your server's documentation for guidance on CORS configuration.

  • If You Don't Control the Server: Consider using a server-side proxy.