Embed API
Put 3D on any website. Drive it from your code.
Every project you publish in MeshMAX can live inside your own site with one tag — as a product viewer, a background, or a scroll story. A small JavaScript API lets your page move the camera, play states, switch configurator options and react to what the visitor does.
No code: Dashboard
You don’t need to write code to embed. In the Dashboard → Embeds you build it by hand and copy the result:
- Pick a published project — or several, for a scroll story.
- Set it up — size, position, background or foreground, and how it behaves while the page scrolls.
- Preview it live on a sample page, exactly as your visitors will see it.
- Copy one snippet and paste it into Shopify, Wix, Webflow, WordPress or any page.
Everything below is for developers who want to go further: the same embed, driven from your own code.
Quick start
- Publish your project in the MeshMAX Studio.
- Copy its project ID — the part after
/c/in the published link. Inmeshmax.app/c/qlu7s1wt1nthe ID isqlu7s1wt1n. - Paste two lines into your page, and give the scene a size with normal CSS.
<meshmax-scene project="YOUR_PROJECT_ID" style="display:block; height:520px"></meshmax-scene>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>That's the whole setup: no build step, no other library. Load the script once per page, however many scenes you put on it.
Playground
A real scene on this page. Each button calls the API, and the panel shows the exact line it ran. Paste the ID of one of your own published projects to try it with yours.
// Press a button above
Layouts
The same scene can sit in your page four ways. Pick one with the layout attribute.
inline · default
A box in your layout, like an image or a video. You set its size.
background
Fixed and full-screen behind your content. Clicks go through to your page.
foreground
Fixed and full-screen over your content, always see-through. Clicks still go through.
pinned
A scroll story: the 3D holds on screen while the visitor scrolls through a tall section, and its scroll timeline plays from start to end.
Attributes
| Attribute | What it does |
|---|---|
project | Required. The published project ID. Change it and the scene swaps to the new project. |
layout | inline (default), background, foreground or pinned — see Layouts. |
transparent | No background colour, so your page shows through behind the 3D. A foreground is always transparent. |
interactive | For a background: lets visitors turn the camera. Without it the layer stays calm and never catches a click. Don't put it on a foreground — see the warning under Recipes. |
scroll | What drives a scroll timeline in an inline scene. element (default) plays it as the scene passes through the screen; page plays it across the whole page; manual leaves it to your page, through setScrollProgress() (see Methods). |
ui | none hides the scene’s own buttons (View in AR, home, share, settings) — for a scene used as a backdrop that takes no clicks. |
zoom | off stops the mouse wheel and a pinch from zooming the scene, so over it the wheel scrolls your page. on lets an interactive background or foreground zoom with the wheel even on a page that scrolls. Without it the project's own setting applies, and a turnable layer leaves the wheel to a page that scrolls. |
fit | adaptive widens the view on narrow and portrait screens, so the object isn't cropped on a phone. |
Only project can change while the scene is on the page. The others are read when the scene loads.
Methods
Each scene carries its own API on the element: element.api. Several scenes on one page never get in each other's way.
const scene = document.querySelector('meshmax-scene');
scene.addEventListener('meshmax:ready', () => {
scene.api.goToView('Front'); // the scene has loaded — safe to drive it
});meshmax:ready. element.api is empty (null) until the scene has loaded, so code that runs straight away should wait for the event.Camera
| Method | What it does |
|---|---|
goToView(name, seconds) | Flies the camera to a view you saved in the Studio, by its name (or id). seconds is optional (0.7). Returns true if the view exists. |
nextView() · prevView() | Steps through the saved views in their order, wrapping around. |
getViews() | The saved views: [{ id, name }]. |
setAutoRotate(on) | Turns the slow automatic turn on (true) or off (false). |
Objects and states
| Method | What it does |
|---|---|
goToState(name, seconds) | Plays a state you captured in the Studio's Animate panel — every object that has a state with that name moves to it. seconds is optional (0.4). |
getModels() | The objects and their states: [{ id, name, states: [{ id, name }] }]. |
Configurator
In projects with a configurator.
| Method | What it does |
|---|---|
selectVariant(variantId) | Picks an option, exactly as if the visitor clicked it: materials, animation, camera, price and summary all follow. |
showHotspots(on) | Shows or hides the hotspots. |
showDimensions(on) | Shows or hides the dimension lines. |
Scroll and chapters
| Method | What it does |
|---|---|
setProgress(percent) | Scroll-timeline projects: jumps the timeline to a point, 0 to 100. The next scroll takes over again. |
sequencer.go(index) | Sequencer projects: goes to a chapter, counting from 0. |
sequencer.next() · sequencer.prev() | The next or previous chapter. |
sequencer.chapters() | The chapters: [{ id, name }]. sequencer.count is how many there are. |
sequencer.autoPause() · sequencer.autoResume() | Pauses or resumes chapters that play on their own. |
element.setScrollProgress(p) | On the element itself, not element.api. With scroll="manual", tells the scene how far through its story your page is, from 0 to 1 — for a page with its own scroll layout. Safe to call before the scene has loaded. |
A method that doesn't apply to a project isn't there: scene.api.selectVariant is undefined in a project without a configurator. Check before you call it.
Events
The scene tells your page what happens inside it with normal DOM events. They bubble, so you can listen on the element or on document. The details are in event.detail.
| Event | When · detail |
|---|---|
meshmax:ready | The scene has loaded and is showing. { models } — how many objects it has. |
meshmax:variantchange | A configurator option was picked, by the visitor or by selectVariant. { variantId, groupId } |
meshmax:hotspot | The visitor opened a hotspot. { id, kind } — kind is what it does: tooltip, card, flyTo, link… |
meshmax:scrollprogress | The scroll timeline moved. { progress } from 0 to 1. |
meshmax:handcontrol | Hand control (camera by hand gestures) was turned on or off. { on } |
meshmax:error | The scene could not be loaded (a wrong ID, or not published). { project, message } — put something else in its place if you like. |
document.addEventListener('meshmax:hotspot', (e) => {
console.log('Hotspot opened:', e.detail.id, 'in', e.target.getAttribute('project'));
});Recipes
Every recipe here started as a test page, and each one was tried on a real published project first.
Your page and the scene
Buttons on your page that move the camera
<meshmax-scene id="product" project="YOUR_PROJECT_ID" style="display:block; height:520px"></meshmax-scene>
<button data-view="Front">Front</button>
<button data-view="Side">Side</button>
<button data-view="Detail">Detail</button>
<script>
const product = document.getElementById('product');
document.querySelectorAll('[data-view]').forEach((btn) => {
btn.addEventListener('click', () => {
if (product.api) product.api.goToView(btn.dataset.view); // the view names you saved in the Studio
});
});
</script>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>Send the visitor's choice to your shop
Option ids are MeshMAX's own. Map them to your product codes once, then react to every pick.
const SKU = {
'var-red': 'CHAIR-RED', // your option ids → your product codes
'var-green': 'CHAIR-GREEN',
};
document.querySelector('meshmax-scene').addEventListener('meshmax:variantchange', (e) => {
const sku = SKU[e.detail.variantId];
if (sku) document.querySelector('#add-to-cart').dataset.sku = sku;
});To find the ids, log them once: addEventListener('meshmax:variantchange', (e) => console.log(e.detail)), then click through the options.
Layers
A 3D background behind your hero
Your content must sit above the scene: give it position: relative and a z-index of 1 or more. Clicks go through the scene to your page.
<meshmax-scene project="YOUR_PROJECT_ID" layout="background"></meshmax-scene>
<section style="position:relative; z-index:1; min-height:100vh">
<h1>Your headline, over the 3D</h1>
</section>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>A background visitors can turn, with your buttons on top
With interactive, the background answers the mouse and touch. Whatever lies over it has to let the pointer through (pointer-events: none) — and switch it back on (auto) only for what should take clicks.
<meshmax-scene project="YOUR_PROJECT_ID" layout="background" interactive></meshmax-scene>
<style>
.overlay { position: fixed; inset: 0; z-index: 2; pointer-events: none; } /* drags pass through to the 3D */
.overlay button { pointer-events: auto; } /* but the button still clicks */
</style>
<div class="overlay">
<h1>Turn the product behind this text</h1>
<button>Add to cart</button>
</div>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>3D behind and in front of your content
Two projects and your page in between: an environment behind, a product floating over the text. The background can be interactive; the gaps between your cards let drags reach it.
<meshmax-scene project="ENVIRONMENT_ID" layout="background" interactive></meshmax-scene>
<meshmax-scene project="PRODUCT_ID" layout="foreground"></meshmax-scene>
<style>
.content { position: relative; z-index: 1; pointer-events: none; } /* the gaps pass drags to the background */
.content .card { pointer-events: auto; } /* the cards stay readable and selectable */
</style>
<div class="content">
<section class="card">Your content, between the two 3D layers</section>
</div>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>interactive it takes every click, tap and scroll, and the page underneath stops working. Make the background interactive instead.Scroll
These need a project with a scroll timeline (Studio → Animate → Scroll). Your page scrolls normally the whole time — nothing is taken over, nothing snaps.
The whole page drives the 3D
As a background, the timeline follows the whole page: the top of the page is the start, the bottom is the end.
<meshmax-scene project="YOUR_PROJECT_ID" layout="background"></meshmax-scene>
<main style="position:relative; z-index:1">
<section style="min-height:100vh">The start</section>
<section style="min-height:100vh">The middle</section>
<section style="min-height:100vh">The end</section>
</main>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>A scene per section
An inline scene plays its timeline as it crosses the screen: it starts as it comes in at the bottom and ends as it leaves at the top. Several on one page each play on their own, as their section arrives — they can be different projects.
<p>Your content…</p>
<meshmax-scene project="FIRST_ID" style="display:block; height:100vh"></meshmax-scene>
<p>More content…</p>
<meshmax-scene project="SECOND_ID" style="display:block; height:100vh"></meshmax-scene>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>Add scroll="page" to an inline scene to have it follow the whole page instead.
A scroll story that holds in place
A pinned scene is a tall runway. The 3D locks on screen when it reaches the top, plays its timeline while the visitor scrolls, and lets go at the end. The height is the length of the story: 300vh is about three screens.
<style>
.story { position: relative; }
.story meshmax-scene { display: block; height: 300vh; }
.steps { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.step { height: 100vh; display: flex; align-items: center; justify-content: center; }
.step div { pointer-events: auto; max-width: 480px; }
</style>
<div class="story">
<meshmax-scene project="YOUR_PROJECT_ID" layout="pinned" transparent></meshmax-scene>
<div class="steps">
<div class="step"><div>The story starts.</div></div>
<div class="step"><div>It holds and animates.</div></div>
<div class="step"><div>And it finishes.</div></div>
</div>
</div>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>Want your page to follow the story too? Listen for meshmax:scrollprogress and fade your own elements in and out with e.detail.progress.
3D in the midground
The same pinned story with three layers: a colour or image per section at the back, the 3D held in the middle, your text in front. The backdrop changes behind the object as the visitor scrolls.
<style>
.story { position: relative; }
.bands { position: absolute; inset: 0; z-index: 0; } /* back */
.bands div { height: 100vh; }
.story meshmax-scene { display: block; height: 300vh; position: relative; z-index: 1; } /* middle */
.steps { position: absolute; inset: 0; z-index: 2; pointer-events: none; } /* front */
.step { height: 100vh; display: flex; align-items: center; justify-content: center; }
.step div { pointer-events: auto; max-width: 480px; }
</style>
<div class="story">
<div class="bands">
<div style="background:#f3f3f5"></div>
<div style="background:#8a8a93"></div>
<div style="background:url(your-image.jpg) center / cover"></div>
</div>
<meshmax-scene project="YOUR_PROJECT_ID" layout="pinned" transparent></meshmax-scene>
<div class="steps">
<div class="step"><div>On white.</div></div>
<div class="step"><div>On grey.</div></div>
<div class="step"><div>On your image.</div></div>
</div>
</div>
<script type="module" src="https://studio.meshmax.app/js/v1/meshmax.js"></script>transparent is what lets the backdrop show through the scene.
Good to know
- Only published projects can be embedded, and anyone with the ID can view them — the same as the published link.
- Republishing updates every embed. Sites showing the scene pick up the new version within a few minutes.
- Size it with CSS. An inline scene fills its element — give the element a height, or it has none.
- One script, many scenes. Load
meshmax.jsonce; every<meshmax-scene>on the page shares it. - Mind the phone. Each scene is a live 3D view. One or two per screen keeps a page smooth on mobile.
- Layers stack like this: a background sits at the very back, a foreground above everything, and your content in between needs
position: relativeand az-indexof 1 or more. - Fixed layers on phones: add
overscroll-behavior: nonetohtml, body, so the page's bounce at the top and bottom doesn't shift a background or foreground scene. - If a scene can't load (wrong ID, unpublished), it shows a short message in its place, fires
meshmax:error, and the rest of your page carries on.
Building something bigger, or need an API we don't have yet? [email protected]