Embed API
Embed the 3D configurator on your storefront and drive it from your own UI.
There are two ways to use the configurator:
- Shopify theme block (no code) — add the Jewelry Configurator block to a product page and set the options in the theme editor. Turn on Show built-in controls if you want the built-in gem/metal/ring switcher.
- Direct iframe (developers) — embed the iframe yourself and drive it from your own buttons via
postMessage. That is what the rest of this page covers.
1. Embedding the iframe
<iframe
src="https://jewelshop.ai/demo/?embed=1"
width="100%"
height="550"
style="border:none;"
allowfullscreen></iframe>
URL parameters
| Param | Values | Default | Meaning |
|---|---|---|---|
embed | 1 | — | Required. Runs the chromeless embed (no landing page, no intro). |
controls | 0 / 1 | 0 | 1 shows the built-in gem/metal/ring UI. Omit / 0 = clean viewer you drive yourself. |
gem | diamond, ruby, emerald, blue_topaz, amber | diamond | Initial gemstone. |
metal | silver, gold, rose_gold | silver | Initial metal. |
ring | 0, 1, 2 | 0 | Initial ring style. |
bg | RRGGBB (hex, no #) | — | Loading-screen background colour. |
shop | your-store.myshopify.com | — | License identity. The Shopify theme block sets this automatically; direct integrators need a license (contact us). |
By default the embed is chromeless — just the rotating 3D viewer. Add &controls=1 only if you want our built-in switcher instead of your own UI.
2. Driving it from your own UI (postMessage)
Every message — both directions — carries source: "jewelshop". Ignore any message without it.
Handshake — wait for ready
The viewer posts ready as soon as it is listening, and again once the 3D renderer has finished loading. Wait for it before sending commands.
const frame = document.querySelector('iframe').contentWindow;
window.addEventListener('message', (e) => {
if (e.data?.source !== 'jewelshop') return;
if (e.data.kind === 'ready' && e.data.rendererLoaded) {
// Safe to send commands now.
}
});
Commands you send (host → viewer)
| kind | Payload | Effect |
|---|---|---|
apply-preset | { preset: { gem, metal, ring } } | Change gem/metal/ring (any subset). Replies preset-applied. |
set-camera | { position:[x,y,z], target:[x,y,z], duration?, ease? } | Move the camera to a fixed angle. Replies camera-set. |
set-controls | { visible: true | false } | Show/hide the built-in UI at runtime. Replies controls-set. |
pause | — | Stop rendering + input (park a hidden viewer). Replies paused. |
resume | — | Resume rendering + input. Replies resumed. |
embed-init | { preset? } | Only needed if you embed WITHOUT ?embed=1; boots embed mode. |
frame.postMessage(
{ source: 'jewelshop', kind: 'apply-preset', preset: { gem: 'ruby', metal: 'gold' } },
'*'
);
Events you receive (viewer → host)
| kind | When |
|---|---|
ready | Viewer is listening (rendererLoaded:false), then again when the 3D scene is loaded (rendererLoaded:true). |
preset-applied | After apply-preset. Carries the resulting { gem, metal, ring }. |
camera-set | After set-camera. |
controls-set | After set-controls. Carries { visible }. |
paused / resumed | After pause / resume. |
close-request | The user pressed Escape inside the viewer. |
3. Fullscreen
The iframe already allows fullscreen. Wrap it and fullscreen the wrapper so your own buttons stay on top:
<div id="jc-wrap" style="position:relative">
<iframe id="jc" src="https://jewelshop.ai/demo/?embed=1" allowfullscreen></iframe>
<button style="position:absolute;top:12px;right:12px"
onclick="document.getElementById('jc-wrap').requestFullscreen()">
Fullscreen
</button>
</div>
4. Example — "View in 3D" over a photo gallery
Show product photos; on click, swap in the 3D viewer and resume it. Hide it again and pause to save resources.
<div id="gallery"><!-- your photos --></div>
<iframe id="jc" src="https://jewelshop.ai/demo/?embed=1"
style="display:none" allowfullscreen></iframe>
<button id="view3d">View in 3D</button>
<script>
const frame = document.getElementById('jc');
const send = (kind, extra = {}) =>
frame.contentWindow.postMessage({ source: 'jewelshop', kind, ...extra }, '*');
document.getElementById('view3d').onclick = () => {
document.getElementById('gallery').style.display = 'none';
frame.style.display = 'block';
send('resume');
};
// Your own gem buttons, for example:
document.querySelectorAll('[data-gem]').forEach((b) =>
b.addEventListener('click', () =>
send('apply-preset', { preset: { gem: b.dataset.gem } })));
</script>
Commands are display-only and safe to send cross-origin — they never touch billing or licensing.
On a licensed Shopify store the theme block handles the
shop identity and the license check automatically; you only need the postMessage layer for custom UI.