Embedding the 3D Viewer
Drop the Innerscene 3D viewer into any web page with a one-line iframe, then control the starting view (camera, render mode, layers, lighting) entirely through URL parameters.
Open the 3D ViewerQuick Start
The simplest embed points at a hosted model file. Paste this into any HTML page and swap in your own file URL:
<iframe
src="https://www.innerscene.com/tools/3d-viewer?url=https://example.com/model.glb&embedded=1"
width="800" height="600" frameborder="0" allowfullscreen>
</iframe>The viewer auto-detects the format from the file content, so any of the supported formats (SKP, DWG, STEP, IFC, Rhino, glTF, OBJ, STL, and more) works with the same snippet. The fastest way to generate a snippet is the Embed button in the viewer toolbar, which fills in your current view automatically.
Compressed vs Readable Links
The Embed dialog offers two ways to encode the same view. Both load identically; they differ only in length and legibility.
Compressed
Every parameter is packed into a single opaque ?d= token. Shortest, but not hand-editable. Best when you just want the link the dialog gives you.
?d=q1bKTEktSi0uVrJSykwtVrIqS8wpTVWyUg...Readable
Every parameter is spelled out in the query string. Longer, but you can read and edit it by hand (change the view, tweak a camera value, toggle a layer).
?url=...&v=top&s=wireframe&gm=gridSource Parameters
Pick one of these to tell the viewer which model to load. Use url for the readable form; d and share are produced by the Share and Embed dialogs.
| Param | Values | Description |
|---|---|---|
url | public file URL (URL-encoded) | Loads a model from any publicly reachable URL. The format is auto-detected from the file content, so the extension does not matter. url=https://example.com/model.glb |
d | compressed token | A single self-contained token that packs the file URL and every view-state parameter below. Produced by the Share and Embed dialogs. Shorter and opaque (not hand-editable). d=q1bKTEktSi0uVrJSykwt... |
share | share id | Loads a server-stored share record (created by the Share button when the file was uploaded rather than linked). Carries its own saved view state. share=4UW0tgPl3fFf6fhBp9xTV7 |
url (data: URI) | data:<mime>;base64,<payload> | The same url parameter also accepts a data: URI, which carries the file inline with no server involved. Decoded in the page — nothing is fetched or uploaded. Must be passed through encodeURIComponent(), otherwise the + characters in the base64 body arrive as spaces. Practical up to a few megabytes; past that a URL or postMessage is faster. url=data%3Amodel%2Fstl%3Bbase64%2Cc29saWQ... |
url (blob: URL) | blob:<origin>/<uuid> | Accepted, but only resolves inside the origin that created it. A blob: URL minted by a parent page cannot be read by the viewer in a cross-origin iframe — the browser refuses the lookup and no workaround exists. Use it only on the viewer page itself; for embeds, post the bytes with postMessage instead. url=blob%3Ahttps%3A%2F%2Fexample.com%2F9f2c… |
Display Parameters
| Param | Values | Description |
|---|---|---|
embedded | 1 | Hides the toolbar and surrounding page chrome for a clean, chromeless embed. Omit it to keep the full toolbar (open, share, export, layers, lighting). embedded=1 |
pm | 1 | Enables the postMessage API, letting the host page hand the viewer a model directly instead of naming a URL. Off unless set. See the postMessage section below. pm=1 |
View-State Parameters
These set the starting view. Add them to a ?url= embed (readable form), or let the Embed dialog capture them for you. Defaults are skipped, so a plain embed simply opens in the default 3D solid view.
| Param | Values | Description |
|---|---|---|
v | 3d, top, bottom, front, back, left, right | Starting view preset. 3d is an orbit view; the rest are orthographic. Skipped when it equals the default (3d). Overridden by c when both are present. v=top |
s | solid, wireframe, x-ray, blueprint, clay | Render mode (style). Defaults to solid. s=wireframe |
c | px,py,pz,tx,ty,tz[,zoom] | Exact camera position (px,py,pz), look-at target (tx,ty,tz), and optional zoom. This is how pan, zoom, and orbit angle are captured. When present it wins over v, so the embed opens framed exactly as the sender had it. c=10.5,20.3,15.2,0,0,0,1.2 |
gm | none, plane, grid | Ground visualization under the model. Defaults to none. gm=grid |
3 | 1 | DWG only: prefer the 3D view of solids when the drawing contains them, instead of the flat plan view. 3=1 |
l | pipe-separated layer names (URL-encoded) | Layers to load hidden. Names are joined with a pipe (|) then URL-encoded. Everything not listed stays visible. l=Body%7CScrews |
L | pipe-separated key:value segments (URL-encoded) | Compact lighting state: only the fields that differ from the default are listed, as short key:value segments joined by a pipe (|), then URL-encoded. Keys include E (environment preset), I (environment intensity), A (ambient), H (hemisphere), and K/B/R (key/back/rim lights as on,intensity,azimuth,elevation). Produced when you adjust the Lighting panel; easiest to copy from the Embed dialog rather than write by hand. L=E%3Asunset%7CI%3A1.2 (decodes to E:sunset|I:1.2) |
Loading a Model Without a URL
?url= needs an address the viewer can fetch. Three common cases have no such address: the host page already holds the bytes in memory, the file sits behind authentication the iframe cannot replay, or the file is on the visitor’s own machine. There are two ways to load a model in those situations.
1. Inline with a data: URI
Small models can travel inside the URL itself. The viewer decodes them in the page — nothing is fetched and nothing is uploaded.
const dataUri = 'data:model/stl;base64,' + base64Bytes;
iframe.src = 'https://www.innerscene.com/tools/3d-viewer'
+ '?url=' + encodeURIComponent(dataUri)
+ '&embedded=1';encodeURIComponent is required. Without it the + characters in a base64 body arrive as spaces and the payload will not decode. Browsers cap URL length, so treat a few megabytes as the practical ceiling — past that, use postMessage.
2. postMessage (any size)
Add ?pm=1 and the viewer will accept the file from the page that embeds it. Bytes are transferred in memory, so there is no size ceiling beyond available RAM and no network round trip. Wait for the viewer-ready handshake before posting — the frame ignores anything that arrives before its listener mounts.
const iframe = document.querySelector('iframe');
window.addEventListener('message', async (e) => {
if (e.source !== iframe.contentWindow) return;
if (e.data?.source !== 'innerscene-3d-viewer') return;
if (e.data.type === 'viewer-ready') {
const buffer = await file.arrayBuffer();
iframe.contentWindow.postMessage(
{ type: 'load-model', name: 'part.stl', data: buffer },
'https://www.innerscene.com',
[buffer], // optional: transfer instead of copy
);
}
if (e.data.type === 'load-result') {
console.log(e.data.ok ? 'loaded ' + e.data.name : 'failed: ' + e.data.error);
}
});| Message | Direction | Fields |
|---|---|---|
| viewer-ready | viewer → host | api — protocol version (currently 1) |
| load-model | host → viewer | data — ArrayBuffer, Uint8Array, Blob, File, or a data: URI string. name — optional filename. mimeType — optional hint. |
| load-result | viewer → host | ok, then either name and bytes, or error. |
Format detection is content-based, so name is only a hint — a model loads correctly even with the wrong extension or none at all. Only the immediate parent frame may post; messages from other frames are ignored.
Loading from the visitor’s own machine. Pointing ?url= at a local web server such as http://127.0.0.1:8000/model.stl looks like the obvious solution and generally will not work: a page served over https is gated by the browser’s local-network permission when it reaches into 127.0.0.1, and a blocked request fails with a bare “Failed to fetch” before anything reaches the server. CORS headers cannot fix it, because no request is sent. Read the bytes in the host page and post them in instead — or just let the user drag the file onto the viewer.
A Worked Example
This embed opens a model in a top-down wireframe view, over a grid ground, with two layers hidden and the toolbar off:
<iframe
src="https://www.innerscene.com/tools/3d-viewer?url=https://example.com/part.step&v=top&s=wireframe&gm=grid&l=Fasteners%7CWelds&embedded=1"
width="800" height="600" frameborder="0" allowfullscreen>
</iframe>Reading the query string: v=top (top view), s=wireframe (wireframe), gm=grid (grid ground), l=Fasteners|Welds (those two layers hidden), embedded=1 (no toolbar).
Responsive Embeds
An iframe has no intrinsic aspect ratio, so to make the viewer fill its container set width="100%" and give the iframe a fixed or aspect-ratio height with CSS:
<div style="position: relative; width: 100%; aspect-ratio: 4 / 3;">
<iframe
src="https://www.innerscene.com/tools/3d-viewer?url=https://example.com/model.glb&embedded=1"
style="position: absolute; inset: 0; width: 100%; height: 100%; border: 0;"
allowfullscreen>
</iframe>
</div>The Embed dialog’s “Responsive width” checkbox emits the width="100%" form for you.
Notes & Limits
- The
urlyou embed must be publicly reachable (no login wall), and the host should allow cross-origin fetches (CORS) so the browser can download the file. - Files load and render in the visitor’s browser. Nothing is uploaded to Innerscene for a
?url=embed. - Use a
sharelink when the file is not hosted on a public URL: the Share button uploads it and stores the view state for 90 days. - Remember to URL-encode values that contain spaces or special characters, especially layer names in
l=.
Also see: 3D Viewer · DWG Viewer · SKP Viewer · STEP Viewer