Jewelry Configurator Live demo →

Embed API

Embed the 3D configurator on your storefront and drive it from your own UI.

There are two ways to use the configurator:

  1. 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.
  2. 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

ParamValuesDefaultMeaning
embed1Required. Runs the chromeless embed (no landing page, no intro).
controls0 / 101 shows the built-in gem/metal/ring UI. Omit / 0 = clean viewer you drive yourself.
gemdiamond, ruby, emerald, blue_topaz, amberdiamondInitial gemstone.
metalsilver, gold, rose_goldsilverInitial metal.
ring0, 1, 20Initial ring style.
bgRRGGBB (hex, no #)Loading-screen background colour.
shopyour-store.myshopify.comLicense 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)

kindPayloadEffect
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.
pauseStop rendering + input (park a hidden viewer). Replies paused.
resumeResume 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)

kindWhen
readyViewer is listening (rendererLoaded:false), then again when the 3D scene is loaded (rendererLoaded:true).
preset-appliedAfter apply-preset. Carries the resulting { gem, metal, ring }.
camera-setAfter set-camera.
controls-setAfter set-controls. Carries { visible }.
paused / resumedAfter pause / resume.
close-requestThe 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.