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.
Getting a widget link
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, orhttps://configo.org/widget/{configurator_uuid}?token=<jwt>for a specific one. - An embed snippet that loads
/widget/script.jsand 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})
}