---
title: "Embedded Designer"
description: "Let organizers create and edit charts inside your product with workspace-scoped, reviewable browser sessions."
---

Embed the full SeatLayer Designer in your CMS without creating a SeatLayer login
for every organizer. Your server owns tenancy and authorizes the request;
SeatLayer mints the short-lived session, and the SDK owns iframe lifecycle and
message validation. The
[seat map designer](https://seatlayer.io/venue-designer/) page shows what
organizers get inside that session.

## End-to-end flow

1. Create or select a chart in the organizer workspace.
2. Authorize the current host user and decide their Designer authority.
3. On authenticated intent or page load, start your backend's `dse_…` session
   request while the SDK and editor shell load.
4. Mount the returned `designerUrl` as soon as both are ready.
5. React to saved, published, close, and error events.
6. Replace the session before or after expiry.
7. Revoke it when access ends.

## Mint from the server

```ts title="server/designer-session.ts"
export async function createDesignerSession(input: {
  workspaceId: string;
  chartId: string;
  allowedOrigin: string;
}) {
  const response = await fetch(
    "https://api.seatlayer.io/v1/designer/sessions",
    {
      method: "POST",
      cache: "no-store",
      headers: {
        authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        ...input,
        authority: "edit",
        mode: "safe",
        expiresInSeconds: 3600,
      }),
    },
  );

  if (!response.ok) throw new Error("Unable to open Designer");
  return response.json();
}
```

Use `read-only` for review, `edit` for draft work, or `publish` only when the host
user is allowed to release a chart. Safe mode is a useful default for delegated
venue editors.

SeatLayer generates and hash-registers the `dse_…` token. Your backend must first
authenticate the organizer, verify tenant/workspace/chart ownership, choose the
authority and exact origin, and then call this endpoint with `sk_…`. Never call
it from browser code, expose `sk_…`, or try to mint a Designer token client-side.
Return the scoped session through a `Cache-Control: no-store` host response.

Do not wait for the final **Open Designer** click if the user is already
authorized. Start the host session request on route intent or authenticated page
load and keep its promise in memory. Alternatively, include the returned
`designerUrl` in an authenticated, no-store server-rendered bootstrap. This moves
the required server trust boundary off the click path without weakening it.

## Mount with JavaScript

```js title="browser/designer.js"
let editor;

async function requestDesignerSession() {
  const response = await fetch("/api/designer-session", {
    method: "POST",
    credentials: "same-origin",
    cache: "no-store",
  });
  if (!response.ok) throw new Error("Unable to open Designer");
  return response.json();
}

// Start both jobs when the authenticated editor route loads. Draw your own
// shell/skeleton immediately; the iframe still waits for designerUrl.
showDesignerShell();
const sdkPromise = import("@seatlayer/js");
const initialSessionPromise = requestDesignerSession();

async function mountDesigner() {
  const [{ EmbeddedDesigner }, { session }] = await Promise.all([
    sdkPromise,
    initialSessionPromise,
  ]);

  editor = new EmbeddedDesigner({
    container: "#designer",
    designerUrl: session.designerUrl,
    expectedChartId: session.chartId,
    expectedWorkspaceId: session.workspaceId,
    height: "fill",
    minHeight: 560,
    showLoadingState: true,
    onReady: () => markEditorReady(),
    onSaved: (message) => saveHostRevision(message.chartId),
    onPublished: (message) => refreshChart(message.chartId),
    onClose: () => closeEditor(),
    onError: (message) => reportSafeError(message.code),
    onRequestRelaunch: async () => {
      const next = await requestDesignerSession();
      editor.setDesignerUrl(next.session.designerUrl);
    },
  });

  editor.mount();
}

void mountDesigner();
```

The Designer iframe cannot fetch the chart until the `dse_…` session exists.
Once mounted, the SeatLayer app already races its authenticated chart/session
read against the lazy Designer bundle. The host-side win is therefore to overlap
session creation with SDK import and shell rendering, not to move session
issuance into the browser.

`height: "fill"` detects whether the container has a definite height. It fills
that box or uses the remaining viewport height, with a minimum of 480 by
default. A numeric height opts into fixed-pixel behavior and legacy resize
messages.

## React

```tsx title="components/VenueEditor.tsx"
import { EmbeddedDesigner } from "@seatlayer/react";

export function VenueEditor({ session, relaunch }) {
  return (
    <EmbeddedDesigner
      designerUrl={session.designerUrl}
      expectedChartId={session.chartId}
      expectedWorkspaceId={session.workspaceId}
      onSaved={({ chartId }) => refreshVenue(chartId)}
      onPublished={({ chartId }) => activateRevision(chartId)}
      onRequestRelaunch={relaunch}
    />
  );
}
```

Resolve `session` in the authenticated route loader or server component where
possible, set the response to no-store, and render the shell while the client
component bundle loads. Keep the raw session only in memory.

## Message safety

The SDK accepts only documented `seatlayer.designer.*` messages from the exact
iframe window and Designer origin. `expectedChartId` and
`expectedWorkspaceId` add host-side resource checks.

| Event | Host response |
|---|---|
| `ready` | Remove outer loading state |
| `saved` | Update host draft status |
| `published` | Refresh event/chart state and show release result |
| `close` | Navigate away or collapse editor |
| `error` | Show safe recovery UI; do not expose raw tokens |

Do not build business logic from the legacy resize message.

## Session replacement

The SDK can proactively relaunch when `onRequestRelaunch` exists. It uses the
session expiry received from the iframe and replaces the iframe with the new
fragment URL. A slept device that wakes after expiry gets one automatic recovery
attempt before the error card appears.

Call `destroy()` when the host unmounts the editor.

## Publication behavior

Publishing updates the chart. Existing events are separately protected:
pristine events may auto-refresh; events with inventory state can require an
explicit map update, and an update fails if booked or held labels would vanish.
Surface this result to the organizer.

## Verification

- [ ] The secret key is never present in browser code or network responses.
- [ ] Workspace and chart ownership are checked before minting.
- [ ] Origin matching is exact in production.
- [ ] Authority matches the host user's role.
- [ ] Safe mode and feature policy match the editing job.
- [ ] Save, publish, close, expiry, relaunch, and revoke flows are tested.
- [ ] The container works at narrow widths and has a usable minimum height.
- [ ] Host code destroys the embed during teardown.

See [embed sessions](/platform/embed-sessions),
[publishing and versioning](/designer/publishing-and-versioning), and
[Designer MCP](/agents/designer-mcp).