Skip to content

Ticket tiers

Carry Adult, Child, Senior, and other per-seat price choices from the chart into a trusted checkout.

Updated View as Markdown

A category can offer multiple ticket tiers for the same inventory—for example Adult, Child, and Senior. The buyer chooses a seat and a tier; SeatLayer freezes that combination into the hold.

Tier model

interface CategoryTier {
  id: string;
  name: string;
  price: number;
}

Tiers are authored on a category. A category without tiers keeps its single base price.

Read selected-seat tiers

browser/tier-controls.jsjs
const chart = new seatlayer.SeatingChart({
  container: "#chart",
  event: "ev_9f3a",
  onSelectionChange: (seats) => {
    for (const seat of seats) {
      if (!seat.tiers?.length) continue;

      renderTierSelect({
        seatId: seat.id,
        options: seat.tiers,
        selected: seat.tierId,
        onChange: (tierId) => chart.setSeatTier(seat.id, tierId),
      });
    }
  },
});

await chart.render();

The selected seat’s price updates when tierId changes. Use that value for immediate buyer display, but use hold inspection on your server for payment.

Change or reset a tier

chart.setSeatTier("seat-42", "child");

// Revert to the category's default/first tier.
chart.setSeatTier("seat-42", null);

An invalid tier/seat combination leaves selection unchanged.

Carry the tier through checkout

Buyer chooses

Selection state carries the tier id and buyer-facing price.

SDK holds

SeatLayer validates the tier against the selected category and freezes the line item.

Browser hands off

Send the opaque holdId to your server. Do not send tier or price as the authority.

Server inspects

GET /v1/events/:eventKey/holds/:holdId returns tierId, unitPrice, currency, and quantity for each authoritative item.

Server books

Charge from inspected items and book using holdId and bookingRef.

server/tier-total.jsjs
const hold = await inspectSeatLayerHold(eventKey, holdId);

const total = hold.items.reduce(
  (sum, item) => sum + item.unitPrice * (item.quantity ?? 1),
  0,
);

await createPayment({
  currency: hold.items[0]?.currency,
  amountMajor: total,
  lines: hold.items.map((item) => ({
    label: item.label,
    tierId: item.tierId,
    unitPrice: item.unitPrice,
    quantity: item.quantity ?? 1,
  })),
});

If a buyer changes a tier after a hold exists, create the updated hold before continuing checkout so authoritative items match the choice. Clear any stale server cart data when the active hold changes.

UX and policy guidance

  • Use buyer-facing tier names, but store stable tier ids on order lines.
  • Explain eligibility requirements for discounted tiers in your own checkout.
  • Do not infer age or accessibility eligibility from the seat itself.
  • Keep tier selection keyboard accessible and associate it with the seat label.
  • Recalculate displayed totals immediately, then verify against server inspection.
  • If a tier disappears after a chart/event update, return the buyer to a clear choice instead of silently substituting another tier.

Verification checklist

  • Every tier has a stable id, readable name, and intended price.
  • Default tier behavior is explicit.
  • Selection updates when setSeatTier() succeeds.
  • The browser forwards only holdId.
  • Server totals use unitPrice, currency, and quantity from inspection.
  • Tier changes replace stale hold/cart state.
  • Eligibility and accessibility copy belongs to the host checkout.

Continue with holds and checkout or complete checkout.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close