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.
It is a self-contained buyer page, not a component. That single fact decides both of the things worth knowing about it.
What it can complete
The framed page runs the same SeatPicker the SDK ships, so the buyer picks and
holds seats for real — those seats are blocked for everyone else while the hold
lasts.
Whether the buyer can also pay depends on the organizer’s account:
- Payments enabled, and a gateway connected to the event — the buyer pays by
card inside the frame, the money goes to the organizer’s own Stripe or
Razorpay account, and the order is confirmed on the SeatLayer buyer page. The
frame is served from
app.seatlayer.io, so that is where the gateway returns the buyer. No host code is involved at any point. - Otherwise — at checkout the buyer sees SeatLayer’s “finish in the ticketing checkout” card. The seats stay held; nothing is charged.
What it never does
Use the iframe when the frame completing the sale (or completing nothing) is acceptable — a marketing page, a venue page, a preview, a layout review.
Use SeatPicker when your page needs to know. It works
without a bundler through the hosted script, and gives you either the server
checkout handoff (onCheckout) or, with checkout: "hosted", the picker’s own
payment plus onOrderConfirmed in your own page.
Responsive embed
<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 no public checkout, selection, hold, or booking-result message — a sale completed inside the frame is invisible to the host page by design, because the frame runs on SeatLayer’s origin and must not be able to assert anything to yours.
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.
npm i @seatlayer/jsyarn add @seatlayer/jspnpm add @seatlayer/jsbun add @seatlayer/jsimport { 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:
const detach = attachPickerFrame(iframe, {
origin: "https://app.seatlayer.io",
});Security requirements
- Compare
event.originwith the iframe URL’s origin. - Compare
event.sourcewithiframe.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 |
| Sells with no backend | Yes, when payments are on | Yes, with checkout: "hosted" |
| Buyer finishes on your domain | No, always on app.seatlayer.io |
In-page gateways yes; a card redirect returns to app.seatlayer.io |
| Your payment/order step before booking | No | Yes |
onCheckout, onOrderConfirmed, 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
- The host page does not need to know about holds or orders.
- 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.
- Anything that must react to a hold or an order uses an SDK surface instead.
Continue with choose an integration or
install SeatPicker.