---
title: "Hosted iframe"
description: "Embed SeatLayer's hosted buyer page, keep it responsive, and understand its direct-booking boundary."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.seatlayer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted iframe

The hosted iframe is the lowest-code way to place a responsive SeatLayer picker
on a page. It loads the published event at `https://app.seatlayer.io/e/:eventKey`
and needs no browser credential.

> **Not a paid-checkout handoff**
>
> The current hosted page directly books the held inventory. Its public
> `postMessage` contract contains resize and fullscreen messages only; it does
> not expose `onCheckout` to your host page. Use `SeatPicker` or `SeatingChart`
> when your server must run payment or order logic before booking.

## When to use it

Use the iframe for:

- no-payment registration or reservation;
- internal event allocation;
- previews and rapid prototypes; or
- a direct-booking experience where the hosted behavior is intentional.

For paid ticketing, use the [hosted script form of `SeatPicker`](/buyer-sdk/install).
It also works without a bundler and provides the server checkout handoff.

## Responsive embed

```html title="event-page.html"
<iframe
  id="seatlayer"
  title="Choose your seats"
  src="https://app.seatlayer.io/e/ev_9f3a"
  style="
    width: 100%;
    aspect-ratio: 4 / 3;
    max-height: min(85vh, 760px);
    border: 0;
    border-radius: 12px;
  "
  allow="fullscreen"
></iframe>

<script>
  const frame = document.getElementById("seatlayer");
  const seatLayerOrigin = new URL(frame.src).origin;

  addEventListener("message", (event) => {
    if (event.origin !== seatLayerOrigin) return;
    if (event.source !== frame.contentWindow) return;

    if (event.data?.type === "seatlayer:height") {
      frame.style.height = `${event.data.px}px`;
      frame.style.aspectRatio = "auto";
    }

    if (event.data?.type === "seatlayer:fullscreen") {
      const on = event.data.on === true;
      frame.style.position = on ? "fixed" : "";
      frame.style.inset = on ? "0" : "";
      frame.style.zIndex = on ? "9999" : "";
      frame.style.maxHeight = on ? "none" : "";
      document.documentElement.style.overflow = on ? "hidden" : "";
    }
  });
</script>
```

Replace `ev_9f3a` with a published event key. `aspect-ratio` reserves stable
space before the first measurement arrives; the height message then tracks
content changes.

## Message contract

| Message | Payload | Host action |
|---|---|---|
| `seatlayer:height` | `{ type: "seatlayer:height", px: number }` | Set the frame height |
| `seatlayer:fullscreen` | `{ type: "seatlayer:fullscreen", on: boolean }` | Pin or restore the frame |

The picker sends these messages because a framed document cannot resize its own
iframe or escape it for fullscreen. There is currently no public checkout,
selection, hold, or booking-result message.

## Use the frame helper

`@seatlayer/js` exports `attachPickerFrame()` for the same protocol. It validates
the frame source, locks and restores document scrolling, preserves prior styles,
handles height changes received during fullscreen, and exits fullscreen on
Escape.

```sh
npm install @seatlayer/js
pnpm add @seatlayer/js
yarn add @seatlayer/js
bun add @seatlayer/js
```

```ts title="host-frame.ts"
import { attachPickerFrame } from "@seatlayer/js";

const iframe = document.querySelector<HTMLIFrameElement>("#seatlayer");
if (!iframe) throw new Error("SeatLayer iframe was not found");

const detach = attachPickerFrame(iframe);

// Call when the iframe is removed or the host view unmounts.
detach();
```

By default the helper accepts the origin parsed from `iframe.src`. To pin it
explicitly:

```ts
const detach = attachPickerFrame(iframe, {
  origin: "https://app.seatlayer.io",
});
```

## Security requirements

> **Verify both origin and source**
>
> A listener that trusts `event.data` alone lets any framed or opened page ask
> your host to resize or cover the viewport.

- Compare `event.origin` with the iframe URL's origin.
- Compare `event.source` with `iframe.contentWindow`.
- Accept only known message types and expected primitive fields.
- Never put a SeatLayer secret key in the iframe URL or host JavaScript.
- Give the iframe an accessible `title`.

## Iframe versus SDK

| Requirement | Iframe | `SeatPicker` |
|---|---:|---:|
| No bundler | Yes | Yes, through hosted script |
| Direct booking | Yes | Optional host-controlled flow |
| Your payment/order step before booking | No | Yes |
| `onCheckout` and hold callbacks | No | Yes |
| Host pricing overrides | No | Yes |
| Host theme overrides | No | Yes |
| Responsive/fullscreen helper | Required in host | Built into same-document widget |

## Verification checklist

- [ ] Direct booking is the intended product behavior.
- [ ] The event is published in the expected mode.
- [ ] The listener verifies both origin and window source.
- [ ] The frame has a useful title and keyboard focus is visible.
- [ ] Height changes do not create horizontal page overflow.
- [ ] Fullscreen locks scrolling and restores all prior styles on exit.
- [ ] Mobile Safari and an Android browser have been tested.
- [ ] Paid flows use an SDK surface instead.

Continue with [choose an integration](/start/choose-an-integration) or
[install `SeatPicker`](/buyer-sdk/install).

Source: https://docs.seatlayer.io/integrations/iframe/index.mdx
