PerformanceGroupPicker is the complete buyer seating step for a run
of two to eight performances that share one published seating chart. It is
available in @seatlayer/js 0.64.0 and later. Try the buyer flow first in the
multi-performance seat selection demo.
Use this page for picker configuration and behavior. Read the end-to-end Performance Group integration for provisioning, checkout, booking, and fulfilment, or the Performance Groups API for exact trusted operations.
It is not a season-ticket, subscription, pass, or renewable season membership. The organizer chooses whether every date is required or partial seats may be sold only on their available dates. Your application still owns the offer, checkout, payment, commercial order, ticket delivery, and support.
Use the separate Fixed Renewable Season integration when the offer needs an immutable 2–20-Event Plan, incumbent Seat Rights, and renewal offers rather than one Performance Group purchase.
Choose the inclusion policy
inclusionMode |
Buyer experience | Compatibility |
|---|---|---|
fixed |
Every date is included. A seat is selectable only when it is available on every performance. | Default; existing integrations keep their current behavior. |
flexible_dates |
A partial seat shows which dates are available. The buyer can select it for those dates and continue from the same chart. | Same-seat flow; the selected dates are returned in the checkout handoff. |
The flexible flow loads one chart and one per-performance availability projection. Selecting or clearing a partial seat re-collapses those cached availability planes locally; it does not load a chart for each date.
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.
For a flexible_dates group, a two-tone partial seat can be opened to see its
available and unavailable dates. Select for 2 available dates updates the
persistent date summary, selects the seat, and submits one atomic hold for only
those two performances. Clearing the selection restores the full date view.
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";
performanceKeys: string[];
seatLabels: string[];
allocations?: Array<{
eventKey: string;
seatLabels: string[];
}>;
}performanceKeys is the chronological set actually held. It contains every
member for a fixed group and the selected available dates for a flexible group.
Treat it, 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 selected 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.