One iframe — no plugins, no 3D engine to build. Your customers rotate the model, switch finishes, and view it at real-world scale in their space with AR, right inside your product pages. Navigation and buttons stay on your side; the viewer just responds.
The buttons below live outside the frame and drive it via postMessage — exactly how you'd wire them to your own UI. On a phone, tap View in AR inside the viewer.
Everything inside the bordered frame is the Placewize embed (a single <iframe>). The variant buttons, background swatches, and QR around it live on your page.
This is the exact embed for what's shown above — it updates live as you change the controls. The project and org slugs are shown as placeholders; swap in your own from your project's share link.
<iframe
src="https://www.placewize.com/embed/viewer/YOUR-PROJECT-SLUG?org=YOUR-ORG-SLUG&vignette=1&bg=dcd7cd&fullscreen=1"
allow="xr-spatial-tracking; camera; accelerometer; gyroscope; fullscreen"
style="width:100%; height:600px; border:0"
></iframe>Paste this where you want the viewer. Replace the project and org slugs with your own (find them in your Placewize project's share link). The allow attributes enable AR and fullscreen.
<iframe
src="https://www.placewize.com/embed/viewer/YOUR-PROJECT-SLUG?org=YOUR-ORG-SLUG&fullscreen=1"
allow="xr-spatial-tracking; camera; accelerometer; gyroscope; fullscreen"
style="width:100%; height:600px; border:0"
></iframe>| org | required | Your organisation slug. |
| variant=<id> | optional | Start on a specific variant (otherwise the default is shown). |
| bg=<hex> | optional | Solid background colour, e.g. bg=0d1117 (the # is optional). |
| fullscreen=1 | optional | Show a fullscreen toggle button in the viewer. |
| transparent=1 | optional | Transparent canvas — the 3D floats over your own page background (overrides bg). |
| bare=1 | optional | Just the 3D window: hides every control (layer switcher, AR button, fullscreen). Drive it entirely from your page via postMessage. |
| ar=1 | optional | Keep the built-in "View in AR" button visible in bare mode (still shows on AR phones only). Pair with bare=1 for a clean viewer that still has native AR. |
The viewer talks to your page over postMessage. Listen for the variant list, then drive it from your own buttons:
// The embed announces itself when it loads, with the list of variants.
window.addEventListener('message', (event) => {
const data = event.data
if (data?.type === 'ready') {
// data.variants → [{ id, name, variantType, isDefault }]
buildYourButtons(data.variants)
}
if (data?.type === 'variantChanged') {
// fired after a switch completes — data.variantId
}
})const frame = document.querySelector('iframe').contentWindow
// Switch the model shown inside the viewer:
frame.postMessage({ type: 'setVariant', variantId: 'the-variant-id' }, '*')
// Change the background colour:
frame.postMessage({ type: 'setBackground', color: '#0d1117' }, '*')
// Launch AR from your own button (handy with bare=1, where the built-in AR button is hidden).
// The viewer tells you when AR is available so you can show/hide your button:
frame.postMessage({ type: 'launchAR' }, '*')Desktop and Android use native fullscreen automatically. iPhone Safari can't fullscreen an element, so the viewer asks your page to resize the iframe instead — handle these two messages (this sandbox already does, which is why the fullscreen button works here on iPhone):
// iPhone Safari can't fullscreen an element, so the viewer asks your page to resize
// the iframe. Handle these two messages to support fullscreen on iOS (and post a
// confirmation back so the viewer's icon stays in sync):
window.addEventListener('message', (event) => {
const frame = document.querySelector('iframe')
if (event.data?.type === 'requestFullscreen') {
frame.classList.add('pw-fullscreen')
event.source.postMessage({ type: 'fullscreenChanged', isFullscreen: true }, '*')
}
if (event.data?.type === 'exitFullscreen') {
frame.classList.remove('pw-fullscreen')
event.source.postMessage({ type: 'fullscreenChanged', isFullscreen: false }, '*')
}
}).pw-fullscreen { position: fixed; inset: 0; width: 100vw; height: 100vh; z-index: 9999; }Desktop visitors can't use AR — so show them a QR to continue on their phone, where AR works (that's the panel next to the viewer above, visible only on desktop). Point the image at our QR endpoint; no need to generate images yourself:
<!-- Scan-to-mobile QR, shown on desktop only (touch devices already have AR).
Point the image at our QR endpoint — no need to generate the image yourself. -->
<div class="pw-qr">
<img
src="https://www.placewize.com/api/qr?data=https%3A%2F%2Fyoursite.com%2Fyour-product-page&size=320"
alt="Scan to view in AR on your phone" width="160" height="160" />
<p>Scan with your phone's camera to view in AR</p>
</div>.pw-qr { display: none; text-align: center; }
@media (hover: hover) and (pointer: fine) { .pw-qr { display: block; } }Endpoint: https://www.placewize.com/api/qr?data=<url-encoded target>&size=256 → returns an SVG. Point data at the page that contains your embed.
A View in AR button appears automatically on AR-capable phones — iPhone (Quick Look) and Android (Scene Viewer). Nothing to configure beyond keeping the allow attributes on your iframe. On desktop it's hidden. Keeping bare=1 but still want the native AR button? Add ar=1. Prefer your own button instead? Leave ar off and launch AR yourself via postMessage (below).
Want the model to sit right inside your own design — no controls, no background, your own AR button? Combine transparent=1 (the 3D floats over your page, no background) and bare=1 (hides every built-in control). The viewer becomes a headless 3D window you drive entirely over postMessage. If you also add ar=1 the native View-in-AR button stays visible (phones only); otherwise it's hidden and you launch AR yourself:
// With ?bare=1 the built-in AR button is hidden — trigger AR from your own control instead.
// The viewer posts { type: 'arAvailability', available } on load and whenever the device/variant
// changes, so you know whether to show your button (AR only works on phones):
window.addEventListener('message', (event) => {
if (event.data?.type === 'arAvailability') {
myArButton.style.display = event.data.available ? 'block' : 'none'
}
})
// then, on click:
myArButton.onclick = () =>
document.querySelector('iframe').contentWindow.postMessage({ type: 'launchAR' }, '*')For a transparent embed, don't set allowtransparency/background on the iframe — just give the iframe's container the background you want the 3D to sit on.