PerformanceGroupPicker is the complete buyer seating step for a fixed run
of two to eight performances that share one published seating chart. It is
available in @seatlayer/js 0.64.0 and later.
It is not a season-ticket, subscription, pass, or buyer-selectable date bundle. The buyer purchases every performance shown by the picker. Your application still owns the offer, checkout, payment, commercial order, ticket delivery, and support.
Choose the selection mode
| Mode | Buyer experience | Use it when |
|---|---|---|
same_seat |
The buyer selects once; those exact seats are held for every included performance. | The run promises the same seat each night. |
per_performance |
The buyer chooses an equal number of seats on each dated tab; seats may differ by performance. | The buyer needs the same party size but not the same physical seats. |
Both modes create one all-or-nothing group hold. The browser never makes a separate hold for each Event.
Before you render
Your trusted backend must create the Performance Group, activate it, and mint a
short-lived bsg_… buyer-access session for the exact browser origin. See the
Performance Groups API for that server-side
setup.
Return only { token, expiresAt } to the browser. Do not put the token in a
URL, persistent browser storage, analytics, or logs.
Same seat for every performance
import { PerformanceGroupPicker } from "@seatlayer/js";
const picker = new PerformanceGroupPicker({
container: "#seat-picker",
performanceGroup: "pg_opening_weekend",
buyerAccessTokenProvider: async () => {
const response = await fetch("/api/seatlayer/group-access", {
method: "POST",
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) throw new Error("Unable to open seat selection");
return response.json(); // { token: "bsg_…", expiresAt }
},
onCheckout: (handoff) => {
// This starts your checkout. It does not book inventory yet.
startCheckout({
performanceGroup: handoff.performanceGroup.key,
holdId: handoff.holdId,
operationId: handoff.operationId,
});
},
onError: (error) => reportSeatSelectionError(error),
});
await picker.render();The picker presents the complete date list, one shared chart, an accessible selection summary, and one Hold seats for all performances action. When the hold completes, the primary action becomes Continue to checkout.
Different seats on each date
Use per_performance only when your offer permits different seats. The picker
reuses one chart and shows dated tabs; it requires the configured party size on
every performance before the hold action becomes available.
const picker = new PerformanceGroupPicker({
container: "#seat-picker",
performanceGroup: "pg_three-night-run",
selectionMode: "per_performance",
numberOfPlacesToSelect: 2,
buyerAccessTokenProvider: getGroupBuyerAccess,
onCheckout: (handoff) => {
beginCheckout({
holdId: handoff.holdId,
operationId: handoff.operationId,
});
},
});
await picker.render();For example, a two-person purchase must have two seats selected on each tab. The buyer cannot hold two seats on Friday and one on Saturday.
Constructor options
| Option | Type | Notes |
|---|---|---|
container |
string | HTMLElement |
Required inline mount target. |
performanceGroup |
string |
Required active pg_… key. Do not pass an Event key. |
selectionMode |
"same_seat" | "per_performance" |
Defaults to same_seat. This is an offer decision made before the picker opens. |
numberOfPlacesToSelect |
number |
Required and at least one for per_performance; each tab must contain this many places. |
buyerAccessTokenProvider |
({ reason }) => Promise<{ token, expiresAt }> |
Preferred group-and-origin-bound credential refresh path. |
buyerAccessToken |
string | { token, expiresAt } |
Non-refreshing alternative; prefer the provider. |
holdTtlMs |
number |
Requested common group hold duration; the server clamps it. |
initialHoldId + initialOperationId |
string pair |
Restore only a matched group hold/operation pair. Supply both or neither. |
restoreHold |
boolean |
Defaults to true; group storage is separate from single-Event holds. |
locale, messages, languages, theme, colorblindSafe |
Presentation options | Use the same accessibility and branding controls as SeatPicker. |
enable3D, max3DSeats, seatView |
Presentation options | The canonical shared chart is rendered once. |
maxSelection, selectedObjects, selectableObjects, selectionValidators |
Selection options | In per-performance mode, exact party size is still required for every date. |
event, custom transport, Best Available, runtime pricing overrides, and
hosted checkout are intentionally unavailable. Passing a group through
SeatPicker or calling one Event’s hold route would make the purchase unsafe.
Checkout handoff
onHold and onCheckout receive this group-specific object:
interface PerformanceGroupCheckoutHandoff {
operationId: string;
holdId: string;
expiresAt: number;
performanceGroup: PerformanceGroupDescriptor;
selectionMode: "same_seat" | "per_performance";
seatLabels: string[];
allocations?: Array<{
eventKey: string;
seatLabels: string[];
}>;
}Treat seatLabels and allocations as buyer display data only. Send the
opaque holdId and operationId to your backend; it must inspect the group
hold before calculating a charge or booking inventory.
The handoff deliberately never includes child Event hold IDs, authoritative prices, payment data, or ticket information.
Hold, recovery, and release
| Buyer state | Picker behavior | Host behavior |
|---|---|---|
| Selecting | Seats are candidates only. | Do not create an order yet. |
| Securing seats | Interaction is locked while the single group operation resolves. | Keep the buyer on the picker; do not start a second hold. |
| Held | Every performance is protected by one common expiry. | Start checkout using the opaque handoff. |
| Conflict | Nothing is presented as held after safe cleanup. | Explain that availability changed and let the buyer choose again. |
| Recovery | The picker checks the same operation after a lost response. | Do not create another operation or guess the outcome. |
| Expired or released | Selection resets and availability refreshes. | Clear the in-progress checkout/cart reference. |
Call picker.destroy() when the host route unmounts. The picker clears its
timers, chart instance, and polling connection. It never books inventory;
booking belongs to your server.
Continue with the end-to-end integration guide and group hold inspection and booking.