Documentation / Widget embedding & iframe API

Widget embedding & iframe API

Embed the Configo widget in an external system and control it over postMessage — configuration, live product data, and edit round-trips.

The Configo widget is a self-contained calculator/configurator page (/widget/{configurator}?token=...) meant to be embedded as an <iframe> inside another system — typically a CRM. In that scenario order and checkout stay entirely inside the host system; only the configurator itself is used. This page describes how to embed the widget and how to talk to it from the host page over postMessage.

A widget link is scoped to a project and a set of permissions, encoded as a signed token. Generate one from Project → Settings → Widget: pick the permissions the embedded widget should have (whether the built-in cart is shown, which prices are visible, price override/adjustment, document printing), then click Generate. You get:

  • A direct link — https://configo.org/widget?token=<jwt> — opens the list of the project's calculators, or https://configo.org/widget/{configurator_uuid}?token=<jwt> for a specific one.
  • An embed snippet that loads /widget/script.js and opens the widget in a modal <iframe> on click.

The token is the only access control — anyone holding the link can use the widget within the permissions it was generated with. Treat it like a credential; regenerate it if it leaks.

Embedding

Option A — the embed script

<script>
    window.configo = {
        id: 'configo',
        btn: {class: '', style: '', text: 'Open configurator'},
        iframe: {width: '800px', height: '600px'},
        token: '<jwt>',

        // optional — sent as set_config / put_product right after the widget signals ready
        config: {mode: 'calculator'},
        product: null,

        events: {
            onReady: () => {},
            onConfiguratorLoaded: (data) => {},
            onCalculated: (product) => {},
            onAddToCart: (product) => {},
            onProduct: (product, trigger) => {},
            onConfigApplied: (type, payload) => {},
            onError: (error) => {},
        },
    }
</script>
<script id="configo" src="https://configo.org/widget/script.js" referrerpolicy="no-referrer"></script>

This renders a button that opens the widget in a modal <iframe> (or a new tab on mobile). window.configo.setConfig(config) and window.configo.putProduct(product) are available once the script has loaded, for sending messages after the widget is already open; window.configo.close() closes it.

Option B — a raw iframe

You can also embed /widget/{configurator}?token=<jwt> in your own <iframe> directly, without the helper script. The message protocol below works the same way — it doesn't depend on script.js, only on the widget page itself.

Message protocol

Both directions use the same envelope:

{"source": "configo", "type": "...", "payload": {...}}

The widget only accepts messages shaped like this; anything else is ignored. There's no origin check on either side — the token embedded in the widget URL is the actual access boundary, not the postMessage channel.

Events from the widget

Sent by the widget via window.top.postMessage(...). If you're using script.js, these map to window.configo.events.*.

type When payload
ready Once, when the widget has mounted {project_uuid}
configurator_loaded Whenever a specific calculator is displayed or changes {configurator_uuid, name}
post_product On every recalculation, and when the action button is clicked {configurator_uuid, trigger: "change" | "add_to_cart", product}
config_applied After a set_config message was processed {ok: true}
product_applied After a put_product message was processed {ok: true}
error A set_config/put_product message was malformed {code, message}

product has the same shape in every case:

{
    "name": "Custom Table",
    "configuration": {"<control_id>": "<value>", "...": "..."},
    "materials": [{"uuid": "...", "name": "Oak board", "unit": "pcs", "quantity": "2.000"}],
    "parts": [ /* production sheet, same shape as calculated in the configurator */ ],
    "purchase_price": 120.5,
    "sale_price": 199
}

trigger on post_product tells you why it fired: "change" means the customer edited a control (debounced, so you won't get one message per keystroke), "add_to_cart" means they clicked the action button — that's the moment to actually persist the item in your own system, since "change" fires continuously while the widget is open.

Commands to the widget

Post these into the iframe's contentWindow (or use window.configo.setConfig() / .putProduct() if you're using the embed script). Wait for ready before sending anything — messages sent before the widget has mounted its listener are lost.

set_config — configure the widget. Every field is optional and only overrides what you send; anything omitted keeps its previous value.

{
  "mode": "calculator",
  "locale": "en",
  "labels": {
    "add": "Add to order",
    "edit": "Update"
  },
  "styles": {
    "dark": true,
    "vars": {
      "--primary": "#1d4ed8"
    }
  },
  "calculator": "<configurator_uuid>",
  "product": {
    "configuration": {
      "<control_id>": "<value>"
    }
  }
}
Field Type Effect
mode "full" | "calculator" "calculator" hides the cart icon, sidebar and checkout form, regardless of the token's show_cart permission — use this when your system owns orders. "full" restores the default (cart shown if the token permits it).
locale "en" | "ru" | "uk" Switches the widget's UI language immediately, no reload.
labels {add?, edit?} Overrides the action button text (takes precedence over the lbl_add URL param).
styles {dark?, vars?} dark toggles dark mode. vars sets arbitrary CSS custom properties on the widget's root element — use this for fine-grained branding (accent color, radii, etc.) rather than a full theme swap, since the widget doesn't currently expose alternate named themes.
calculator uuid Navigates to that calculator. Only meaningful when the widget was opened on the calculator list (/widget with no uuid in the path).
product product-like object Convenience for pre-filling the calculator in the same round-trip as the rest of the config — equivalent to sending put_product right after. Requires at least configuration.

put_product — load a product into the calculator that's currently displayed, e.g. to let the customer edit an item they previously saved in your system:

{
  "configuration": {
    "<control_id>": "<value>",
    "...": "..."
  }
}

Only configuration is read; the widget recalculates everything else (price, materials, name) itself. This is the same mechanism the widget uses internally when editing a cart item.

Example: calculator-only integration

A CRM that owns its own order flow and only wants the configurator:

<script>
    window.configo = {
        token: '<jwt>',
        config: {mode: 'calculator', locale: 'ru'},
        events: {
            onProduct(product, trigger) {
                if (trigger === 'add_to_cart') {
                    // persist `product` as a line item in the CRM's own order
                }
            },
        },
    }
</script>
<script id="configo" src="https://configo.org/widget/script.js"></script>

To let the customer reopen and edit a previously saved item, open the widget on the same calculator and send put_product once it's ready:

window.configo.events.onReady = () => {
    window.configo.putProduct({configuration: savedItem.configuration})
}