Configura Studio docs
Three ways to embed a 3D product customizer on your site. Pick the one that matches your stack. All three load from the same hosted widget — no build step, no SDK to manage, no dependencies beyond a single script tag.
Quick start
The fastest way to put a live 3D customizer on any page. Replace your-product-slug with the slug from your product's URL in the Configura dashboard.
<script src="https://configurastudio.com/configura-widget.js"></script>
<configura-customizer product="your-product-slug"></configura-customizer>
That's it. The customizer renders inline where you place it, at 700px tall by default. Buyers can rotate, pick colors, add text, and save their configuration — you receive the print-ready file on every order.
/customize/ — for example mountain-key-holder-mnz1mgbc.
Choose an integration
Configura ships three embed paths. They all talk to the same backend and render the same viewer — they differ in how much JavaScript your host page needs.
One HTML tag. Works anywhere web components are supported (which is everywhere modern).
Call ConfiguraWidget.init() from JavaScript. Gives you programmatic control, callbacks, and a returned instance.
A plain <iframe>. No JavaScript. Works in restrictive platforms, email-like CMSes.
| Custom element | Script API | Iframe | |
|---|---|---|---|
| Requires JS in host page | Yes (one script) | Yes | No |
| Lines of HTML | 2 | 5 | 1 |
| Event listeners | DOM events | Callbacks | postMessage |
| Programmatic destroy | DOM removal | .destroy() | DOM removal |
| Works in React/Vue | Native | via ref | Native |
Order flow: code vs cart
However the customizer is embedded, the buyer eventually needs to communicate their selection back to your store. Configura supports two flows for this. They're not exclusive — you can run code-based on one product and cart on another — but each customizer is configured for one flow at a time.
Buyer customizes, gets a 6-character code, pastes it into your store's existing fields (Etsy Personalization, cart notes, order properties). You match the code to the saved selection in the Order Manager.
Buyer customizes, clicks Add to Cart inside the embed, the embed posts sku and priceCents to your storefront via postMessage. Your storefront creates the line item directly.
| Code-based | Merchant embed | |
|---|---|---|
| Buyer action to order | Paste code into a field | Click Add to Cart |
| Host page integration | None (uses existing fields) | postMessage listener required |
| Etsy compatible | Yes (the only Etsy path) | No (Etsy blocks iframes) |
| Shopify compatible | Yes (via cart note) | Yes (preferred) |
| Order Manager required | Yes (match code to order) | No (cart has selection inline) |
| Merchant registration required | No | Yes |
Merchant embed (cart-enabled)
The merchant embed wires the customizer's Add to Cart button directly into your storefront. It's the right choice for Shopify, BigCommerce, or any custom storefront where you control the product page's JavaScript. Setup involves two pieces: registering your storefront as a merchant in the Configura dashboard, and listening for the cart-add message on your host page.
1. Register your storefront as a merchant
In the dashboard, go to Integrations → Merchants and add your store's URL. The origin you register here is the only one allowed to trigger cart actions — Configura validates the parent origin via postMessage before sending cart events. If the origin isn't registered, the embed still loads and buyers can customize, but the Add to Cart button won't appear.
2. Embed with cart enabled
Add cart="1" on the custom element (or ?cart=1 on the iframe URL). The Add to Cart button mounts inside the embed once the merchant origin handshake completes.
<configura-customizer
product="mountain-key-holder"
cart="1"
sku="MKH-001"
price="2999"
product-name="Mountain Key Holder"
></configura-customizer>
price is in cents (integer). The example above is $29.99. sku and price are the values the embed will post back when the buyer clicks Add to Cart — see SKU & price below for resolution rules.
3. Listen for the cart message on your host page
When the buyer clicks Add to Cart, the embed posts a message to window.parent. Your storefront page listens and adds the line item:
// In your Shopify theme (e.g. main-product.liquid <script>)
window.addEventListener('message', function(e) {
// Only trust messages from configurastudio.com
if (e.origin !== 'https://configurastudio.com') return;
// Configura embeds tag every message with this source string
if (e.data?.source !== 'configura-embed') return;
if (e.data.type !== 'add-to-cart') return;
const { product, meta } = e.data.data;
// Shopify: add line item via the Ajax Cart API
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: /* your variant id, looked up by product.sku */,
quantity: 1,
properties: { 'Configura code': meta.configuraCode }
})
}).then(() => { window.location = '/cart'; });
});
Cart message payload
The full message envelope is:
{
source: 'configura-embed',
version: /* protocol version, e.g. "2" */,
type: 'add-to-cart',
data: {
product: { slug, name, sku, priceCents },
config: { /* full buyer selections — regions, colors, text */ },
preview: { image, mimeType } | null,
meta: { embedUrl, timestamp, merchantId, configuraCode, configHash }
}
}
| Field | Type | Description |
|---|---|---|
data.product.slug | string | Configura product slug |
data.product.name | string | Resolved display name (override wins) |
data.product.sku | string | Resolved SKU (mapping wins, then URL param) |
data.product.priceCents | integer | Resolved price in cents |
data.config | object | Buyer's selections — regions, colors, text. Mirrors the v1 state shape. |
data.preview | object | null | Snapshot of the customizer viewport — image (data URL), mimeType. null if capture failed. |
data.meta.embedUrl | string | The full URL the embed was loaded from |
data.meta.timestamp | ISO string | When the buyer clicked Add to Cart |
data.meta.merchantId | string | null | The merchant ID Configura validated against |
data.meta.configuraCode | string | 6-character order code (e.g. ABC123). Surface this in your line item so sellers can locate the matching order in Configura admin. |
data.meta.configHash | string | null | 12-hex stable hash of the selection — useful for cart-line dedup. null on browsers without SubtleCrypto. |
e.origin AND e.data.source. Always check that incoming messages come from https://configurastudio.com AND that e.data.source === 'configura-embed' before acting on them. Other scripts on the page may post messages with overlapping type values; the source string disambiguates.
Using the Script API instead
If you're using ConfiguraWidget.init() rather than the custom element, pass an onAddToCart callback. The widget unwraps the envelope for you — you get just the data object:
ConfiguraWidget.init({
container: '#configurator',
product: 'mountain-key-holder',
cart: true,
sku: 'MKH-001',
price: 2999,
onAddToCart: function(data) {
// data === e.data.data from the postMessage envelope
// (product, config, preview, meta — same shape as above)
console.log('Add to cart:', data.product, data.meta.configuraCode);
}
});
Custom element users can also use the DOM event API: el.addEventListener('configura-add-to-cart', e => { /* e.detail === data */ }).
SKU & price resolution
When the buyer clicks Add to Cart, the embed needs to know which SKU and price to send. Values are resolved in priority order:
| # | Source | When it's used |
|---|---|---|
| 1 | Merchant product mapping (dashboard) | Wins if a mapping exists for this customizer on this merchant |
| 2 | URL params: ?sku=...&price=... | Used when no merchant mapping is set |
| 3 | Custom element attributes: sku, price | Same as URL params — the element forwards them as URL params on the underlying iframe |
For most setups, leaving the merchant mapping blank and putting SKU and price directly on the embed is fine — the snippet generator at Add to Your Store does this automatically. Use a merchant mapping only when you need to override the embed values for a specific merchant (e.g. a different price on a partner's Shopify than on your own).
<configura-customizer>
A standards-compliant custom element. Drop the script once per page; use the tag anywhere in HTML, JSX, Vue templates, or Svelte components.
<script src="https://configurastudio.com/configura-widget.js"></script>
<configura-customizer
product="mountain-key-holder"
height="620px"
accent="#2a1525"
brand="Nord Haus"
></configura-customizer>
The script can be loaded once in <head> and reused across multiple customizer tags on the same page.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
product * |
string | — | Product slug. Required. |
height |
string | 700px |
CSS height. Accepts 620px, 80vh, or bare 620 (px assumed). |
accent |
hex color | #3b82f6 |
Primary button color inside the viewer. |
brand |
string | — | Shop name shown in the customizer's header. |
hide-branding |
boolean | false | Hide "Powered by Configura" watermark. Requires Enterprise plan. |
hide-header |
boolean | false | Hide the customizer's own top bar (product title + Save button). |
Events
The element dispatches standard DOM CustomEvents that bubble. Listen with addEventListener.
| Event | Fires when | event.detail |
|---|---|---|
| configura-ready | Customizer has loaded and is interactive | Initial state object |
| configura-order-saved | Buyer clicks Save Selection | Selection object with regions, text, and configuration code |
| configura-resize | Customizer's content height changes | { height: number } |
const el = document.querySelector('configura-customizer');
el.addEventListener('configura-order-saved', (e) => {
// e.detail.code is the 7-char customization code
// shown to the buyer and tied to the order
console.log('Buyer saved configuration:', e.detail.code);
});
ConfiguraWidget.init()
The script API is the lower-level surface the custom element is built on top of. Use it when you need a reference to the instance (for later .destroy() or .getState() calls), or when you're mounting the customizer dynamically in response to some user action.
<div id="my-customizer"></div>
<script src="https://configurastudio.com/configura-widget.js"></script>
<script>
const widget = ConfiguraWidget.init({
container: '#my-customizer',
product: 'mountain-key-holder',
height: '620px',
accent: '#2a1525',
onReady: (data) => console.log('ready', data),
onOrderSaved: (data) => console.log('saved', data),
});
</script>
Options
| Option | Type | Description |
|---|---|---|
container * | string | HTMLElement | CSS selector or DOM node to mount into. |
product * | string | Product slug. |
height | string | CSS height. Default 700px. |
width | string | CSS width. Default 100%. |
accent | string | Hex color for primary buttons. |
brand | string | Shop name shown in header. |
hideBranding | boolean | Hide watermark (Enterprise plan). |
hideHeader | boolean | Hide customizer's top bar. |
onReady | function | Called once the customizer is interactive. |
onOrderSaved | function | Called when buyer saves their configuration. |
onResize | function | Called when content height changes. |
Instance methods
The return value of ConfiguraWidget.init() is an instance with these methods:
| Method | Description |
|---|---|
widget.getState(callback) |
Asks the customizer for its current selection. callback receives {regions: [...], ...}. |
widget.destroy() |
Removes the iframe, detaches event listeners, cleans up state. Call before unmounting in SPAs. |
ConfiguraWidget.destroyAll() iterates every live widget on the page and destroys each. Useful during route changes.
Iframe embed
If your host platform blocks external JavaScript — or you just want the simplest possible integration — embed the customizer as a raw iframe. No widget script required.
<iframe
src="https://configurastudio.com/embed.html?product=mountain-key-holder"
width="100%"
height="700"
frameborder="0"
allow="accelerometer; autoplay; gyroscope"
></iframe>
URL parameters
| Parameter | Values | Description |
|---|---|---|
product * | slug | Required product identifier. |
embed | true | Enables embed mode (hides certain nav elements). |
hideBranding | 1 | Hide Configura watermark. Enterprise only. |
hideHeader | true | Hide product title bar. |
accent | hex | URL-encoded hex color — e.g. %232a1525. |
brand | string | URL-encoded shop name. |
postMessage to receive order-save events. The other two methods handle this for you.
Etsy
Etsy doesn't allow JavaScript, iframes, or custom HTML inside listings. Instead of embedding, Configura injects a link to your customizer into the listing description — buyers click through, configure, and come back with a 6-character code they paste into a listing message or variation.
This is set up from the Configura dashboard's integration flow, not docs — connect your Etsy shop, pick a listing, and Configura writes the link in for you.
Shopify
Shopify works with either order flow. Pick based on how integrated you want the cart experience to feel.
Option A — Code-based (simplest)
Embed the customizer with the custom-element snippet. The buyer customizes, gets a 6-character code, and pastes it into the cart-note or order-properties field at checkout. No JavaScript on your end.
<!-- In sections/main-product.liquid (or your product template) -->
<script src="https://configurastudio.com/configura-widget.js"></script>
<configura-customizer
product="{{ product.metafields.configura.slug }}"
height="620px"
></configura-customizer>
Store the Configura product slug in a product metafield (configura.slug) so each Shopify product maps to its matching customizer.
Option B — Merchant embed (cart-enabled)
Add cart="1" plus a SKU and price. When the buyer clicks Add to Cart inside the embed, your theme's listener creates a Shopify line item directly. See the full merchant embed section for the message payload and a complete listener example.
<configura-customizer
product="{{ product.metafields.configura.slug }}"
cart="1"
sku="{{ product.selected_variant.sku }}"
price="{{ product.selected_variant.price }}"
product-name="{{ product.title | escape }}"
></configura-customizer>
Then register your Shopify store as a merchant in Integrations → Merchants so the cart message is trusted. The merchant embed section has the matching theme-side JS that listens for the cart message and POSTs to Shopify's /cart/add.js.
Squarespace
Add the customizer via a Code Block. Squarespace sanitizes some inline scripts — the safest path is the raw iframe method:
<iframe
src="https://configurastudio.com/embed.html?product=your-slug"
width="100%"
height="620"
frameborder="0"
></iframe>
For a white-label experience (custom accent, no watermark) you'll need Enterprise — add &hideBranding=1&accent=%232a1525&brand=Your%20Shop to the src URL.
WordPress & WooCommerce
Drop the custom-element snippet into any page's Custom HTML block, or paste it into your theme's single-product.php template to add it to every product page. WooCommerce buyers can save a customization code as an order note — pair with a plugin that exposes note fields on the cart if you want it captured automatically.
<!-- WordPress Custom HTML block -->
<script src="https://configurastudio.com/configura-widget.js"></script>
<configura-customizer product="your-slug"></configura-customizer>
For a fully integrated cart experience, WooCommerce can also use the merchant embed (cart-enabled) flow — add cart="1", register your store in Integrations → Merchants, and listen for the configura:addToCart postMessage in your theme. The handler typically calls WooCommerce's AJAX endpoint ?wc-ajax=add_to_cart to create the line item.
React & Next.js
The custom element works natively in React — no wrapper required. Load the script once (e.g. in your <Head> or app.tsx), then use the tag like any other JSX element:
// In your app layout, once:
import Script from 'next/script';
<Script src="https://configurastudio.com/configura-widget.js" strategy="afterInteractive" />
// In any component:
export function ProductPage({ slug }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
const handler = (e) => console.log('saved', e.detail);
el?.addEventListener('configura-order-saved', handler);
return () => el?.removeEventListener('configura-order-saved', handler);
}, []);
return <configura-customizer ref={ref} product={slug} height="620px" />;
}
.d.ts file so JSX accepts it:
declare namespace JSX {
interface IntrinsicElements {
'configura-customizer': {
product: string;
height?: string;
accent?: string;
brand?: string;
'hide-branding'?: boolean;
ref?: any;
};
}
}
When your component unmounts (route change, conditional render), the element's disconnectedCallback runs automatically and destroys the underlying widget. No leaks.
Styling & theming
The customizer renders inside an iframe, so you cannot reach INTO it with CSS from your host page. What you can control:
- Outer size: set
heighton the element. Width stretches to fit the container. - Accent color:
accent="#..."changes primary buttons inside the viewer. - Shop name:
brand="Nord Haus"shows in the header. - Chrome hiding:
hide-brandingandhide-headerfor a fuller white-label look. - Wrap in a card: the element is just a block element — wrap it in a div with your own border, padding, or glassmorphism styling.
For deeper customization (custom fonts, full layout changes, translated UI), reach out about a custom embed build.
Troubleshooting
The customizer doesn't load
- Check the browser console for errors.
[Configura] Missing required "product" optionmeans your slug is empty. - Verify your product is published. Draft products won't render.
- Confirm the slug matches exactly — slugs are case-sensitive.
The iframe loads but the model doesn't show
- Likely a CORS or CDN issue with the STL file. Check your browser's Network tab for failed requests.
- Very large STLs (>25MB) may time out on slow connections. Re-export at a lower resolution.
I can't rotate the 3D model
- Rotation requires pointer events to reach the iframe. Make sure no parent element has
pointer-events: none. - On mobile, the customizer captures touches by design (so rotation feels natural). Scroll past it by touching outside the customizer.
My saved camera view isn't showing
- Hard-refresh your browser (⌘⇧R / Ctrl+F5) to bust the cache.
- If the view only updated in the customizer but not in the embed, redeploy — the embed uses the latest published product config.
My custom element isn't rendering
- Ensure
configura-widget.jsloaded before the element needs to upgrade. The order doesn't matter (custom elements upgrade on registration), but the script must load at some point. - Check that the browser supports custom elements — essentially all browsers from 2019 onward do.
Browser support
Configura's customizer uses WebGL via Three.js. It runs on:
- Chrome / Edge 90+
- Firefox 90+
- Safari 14+ (desktop & iOS)
- Samsung Internet 14+
Roughly 98% of global web traffic. The embed gracefully degrades with a "WebGL not supported" message on the fractional cases that can't run it — buyers aren't blocked from checkout, they just can't preview in 3D.
Content Security Policy
If your host site has a strict CSP, whitelist these sources:
| Directive | Value |
|---|---|
script-src | https://configurastudio.com |
frame-src | https://configurastudio.com |
connect-src | https://configurastudio.com https://*.supabase.co |
Ready to ship your first customizer?
Set up a working 3D product page in under 10 minutes. No credit card required.