Editor Embed

/

With the SDK

Editor Embed with the SDK

Plan access

Editor Embeds are available on Free, Pro, and Enterprise, within each plan's usage limits.

Embed the full Moshpit editor inside your own UI. Users edit, save, and publish without leaving your app — and you stay in control of the surrounding chrome.

Minimal example

<>

HTML

<div id="moshpit-editor" style="height: 720px"></div>
 
<script src="https://moshpit.studio/moshpit-embed.js"></script>
<script>
  const editor = Moshpit.editor('#moshpit-editor', {
    publicKey: 'mpk_PUBLIC_KEY',
    sessionEndpoint: '/api/moshpit/editor-session',
    splatId: 'OPTIONAL_SPLAT_ID',
  });
 
  editor.on('ready', () => console.log('editor ready'));
  editor.on('saved', ({ splatId }) => console.log('saved', splatId));
  editor.on('authRequired', () => showSignInPrompt());
  editor.on('dirtyChanged', ({ dirty }) => {
    saveBtn.disabled = !dirty;
  });
</script>

The matching backend endpoint mints an editor session — type 'editor' instead of 'viewer':

TS

TypeScript

// POST /api/moshpit/editor-session
export async function POST(request: Request) {
  const body = await request.json().catch(() => ({}));
  const upstream = await fetch(
    'https://moshpit.studio/api/editor/embed-sessions',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.MOSHPIT_SECRET_KEY}`,
      },
      body: JSON.stringify({
        publicKey: process.env.MOSHPIT_PUBLIC_KEY,
        type: 'editor',
        splatId: body.splatId ?? null,
      }),
    },
  );
 
  return new Response(await upstream.text(), {
    status: upstream.status,
    headers: { 'Content-Type': 'application/json' },
  });
}

If you mint a blank guest editor session without an external user, listen for authRequired. When it fires, sign the user into your app, mint a scoped editor session, and send it back with updateSession before retrying save.

Open an existing splat or create a new one

Pass splatId to open an existing scene. Omit it (or pass null) to start in "create new" mode:

JS

JavaScript

// Edit an existing splat
Moshpit.editor('#moshpit-editor', {
  publicKey: 'mpk_...',
  sessionEndpoint: '/api/moshpit/editor-session',
  splatId: '65f1a2b3c4d5e6f7a8b9c0d1',
});
 
// Start with a blank canvas
Moshpit.editor('#moshpit-editor', {
  publicKey: 'mpk_...',
  sessionEndpoint: '/api/moshpit/editor-session',
});

When the user saves a brand-new splat, the saved event delivers the new splatId:

JS

JavaScript

editor.on('saved', ({ splatId }) => {
  // First save → splatId is now defined
  // Persist this on your side and update the host URL if you want reloads to reopen it
  if (splatId) {
    const url = new URL(window.location.href);
    url.searchParams.set('splatId', splatId);
    history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`);
  }
});

Drive the editor from your UI

Wire your own buttons up to the editor's commands:

JS

JavaScript

saveBtn.addEventListener('click', () => editor.save());
publishBtn.addEventListener('click', () => editor.publish());
newBtn.addEventListener('click', () => editor.createProject());
openBtn.addEventListener('click', () => editor.openProject(splatId));

The full list lives at Editor Commands.

Track unsaved changes

dirtyChanged lets you reflect unsaved state in your UI — disable navigation, show a dot in the title bar, prompt before unmount:

JS

JavaScript

let isDirty = false;
 
editor.on('dirtyChanged', ({ dirty }) => {
  isDirty = dirty;
  saveBtn.disabled = !dirty;
});
 
window.addEventListener('beforeunload', (e) => {
  if (isDirty) {
    e.preventDefault();
    e.returnValue = '';
  }
});

Tear down

Single-page-app users may navigate away. Call editor.destroy() to remove the iframe and clean up listeners and refresh timers.

JS

JavaScript

editor.destroy();

What's next