---
title: "SeatingChart reference"
description: "Headless map options, callbacks, methods, live holds, GA, groups, floors, and error behavior."
---

`SeatingChart` mounts the live map canvas without SeatLayer's cart/tray chrome.
Your application owns totals, tier/GA controls, the checkout button, countdown,
and buyer messages. The
[reserved seating SDK and API overview](https://seatlayer.io/developers/) shows
which surface to choose before you commit to the headless route.

```js title="browser/chart.js"
import { SeatingChart } from "@seatlayer/js";

const chart = new SeatingChart({
  container: "#chart",
  event: "ev_9f3a",
  publicKey: "pk_test_…",
  onSelectionChange: renderCart,
  onError: reportPickerError,
});

await chart.render();
```

For a public Platform event, `publicKey` creates one direct, origin-bound,
Public-sale-only bootstrap; chart geometry and compact inventory status arrive
together, and the resulting bearer remains in memory. For login, presale,
partner, or channel inventory, supply `buyerAccessTokenProvider` or
`buyerAccessToken` from your authenticated backend instead. Either explicit
buyer credential takes precedence over `publicKey`.

## Constructor options and callbacks

| Option | Type | Notes |
|---|---|---|
| `container` | `string \| HTMLElement` | Required, definite dimensions |
| `event` | `string` | Required event key |
| `apiBase` | `string` | Defaults to production API |
| `publicKey` | `string` | Publishable key for direct, exact-origin, Public-sale-only Platform bootstrap |
| `buyerAccessTokenProvider` | `({ reason }) => Promise<{ token, expiresAt }>` | Refreshable private/login/presale/channel access from your authenticated backend; takes precedence over `publicKey` |
| `buyerAccessToken` | `string \| { token, expiresAt }` | One-shot escape hatch; prefer the refreshable provider |
| `maxSelection` | `number` | Default `10` |
| `selectedObjects` | `string[]` | Initial object ids or public labels; applied after live availability loads |
| `selectableObjects` | `string[] \| null` | Allow-list of object ids or public labels; omit/null for every free object |
| `numberOfPlacesToSelect` | `number` | Require an exact seated/table guest count and cap selection at that count |
| `selectionValidators` | `PickerSelectionValidator[]` | Minimum-count, consecutive-seat, and no-orphan local guards |
| `locale` / `messages` | `string` / overrides | Localized on-map copy |
| `currency` | `string` | Display fallback |
| `colorblindSafe` | `boolean` | Accessible palette and hollow booked state |
| `initialView` | `RendererViewMode` | Use `flat`; perspective modes deprecated |
| `seatTooltip` | `boolean` | Built-in hover tooltip, default `true` |
| `onSeatHover` | `(details \| null) => void` | Build a custom hover surface |
| `onSelectionChange` | `(seats) => void` | Manual, group, or tier change |
| `onSelectionValidityChange` | `(state) => void` | Rule state and typed `violations` after each selection change |
| `onSelectionValid` / `onSelectionInvalid` | callback | Rule-validity transition |
| `onSelectionLimit` | `(max) => void` | A selection attempt reached the active cap |
| `onHint` | `(message \| null) => void` | Non-blocking localized advice |
| `onHold` | `(hold) => void` | Any successful hold |
| `onHoldRestored` | `(hold) => void` | `resumeHold()` succeeds |
| `onHoldExpired` | `() => void` | Server expiry reached |
| `onGAClick` | `(area) => void` | Host should request quantity and call `holdGA()` |
| `onDeckTap` | `(floorId) => void` | Synchronize custom floor controls |
| `onError` | `(error) => void` | Public async methods report failures here |

## Selection and hold methods

| Method | Signature/behavior |
|---|---|
| `getMode()` | `"live" \| "test" \| null`; null before render |
| `getSelection()` | Current `SelectedSeat[]` |
| `selectObjects(objects)` / `deselectObjects(objects)` | Select/deselect by engine id or public label |
| `clearSelection()` | Clear every unheld selection |
| `selectCategories(keys)` / `deselectCategories(keys)` | Change every selectable object in named categories |
| `setSelectableObjects(objects)` | Replace the allow-list without remounting; `null` restores all free objects |
| `setMaxSelection(max)` | Change the active selection cap without remounting |
| `getSelectionValidity()` | Exact-count state, or `null` when exact selection is disabled |
| `hold({ ttlMs? })` | Hold current selection; `null` on failure/conflict |
| `resumeHold(holdId)` | Restore without extending |
| `extendHold(ttlMs?)` | Extend active hold within server limits |
| `getCurrentHold()` | Current `HoldResult \| null` |
| `getGAAreas()` | Live GA capacity records |
| `holdGA(areaId, qty, { tierId?, ttlMs? })` | Quantity hold |
| `bestAvailable(qty, categoryKey?, options?)` | Adjacent group hold |
| `setSeatTier(seatId, tierId)` | Change selected tier; null resets |
| `release()` | Release the full hold |
| `releaseLabels(labels)` | Partially release listed labels |

`hold()`, `resumeHold()`, `holdGA()`, and `bestAvailable()` catch public
transport errors, call `onError`, and resolve `null`. Do not wait for an
exception to render buyer recovery.

## Controlled and validated selection

```js
const chart = new SeatingChart({
  container: "#chart",
  event: "ev_9f3a",
  publicKey: "pk_test_…",
  selectedObjects: ["A-10"],
  selectableObjects: ["A-10", "A-11", "A-12"],
  selectionValidators: [
    { type: "minimumSelectedPlaces", minimum: 2 },
    { type: "consecutiveSeats" },
    { type: "noOrphanSeats" },
  ],
  onSelectionValidityChange: ({ isValid, remaining, violations }) => {
    checkoutButton.disabled = !isValid;
    selectionHint.textContent = isValid ? "Ready" : violations.join(", ");
  },
});

await chart.render();
chart.selectObjects(["A-11"]);
```

References accept the stable engine id returned on a selected object or the
public inventory label used by hold/book APIs. Unavailable objects are ignored.
Grouped-table references always select or deselect the whole booking unit.
`hold()` returns `null` and calls `onError` before transport while a rule is
invalid. Existing exact-count failures keep `code: "selection_count_mismatch"`;
validator failures use `code: "selection_invalid"` and include the full
selection state.

## View and lifecycle methods

| Method | Behavior |
|---|---|
| `render()` | Fetch, mount, seed live state; idempotent after success |
| `getFloors()` / `setFloor(id)` | Custom multi-floor navigation |
| `setColorblindSafe(on)` | Runtime accessible palette |
| `setViewMode(mode)` / `getViewMode()` | 2D renderer compatibility |
| `zoomIn()` / `zoomOut()` / `zoomToFit()` | Host map controls |
| `destroy()` | Socket/listener/timer/DOM cleanup |

The full 3D buyer experience belongs to `SeatPicker`; `SeatingChart.initialView`
does not enable that venue view.

## Core result types

```ts
interface HoldResult {
  holdId: string;
  expiresAt: number;
  seats?: SelectedSeat[];
  items?: HoldLineItem[];
}

interface BestAvailableResult extends HoldResult {
  labels: string[];
  zoneId?: string;
}

interface GAAreaAvailability {
  id: string;
  label: string;
  capacity: number;
  available: number;
  categoryKey: string;
  price: number;
  currency: string;
  displayLabel?: string;
  displayType?: string;
  tiers?: Array<{ id: string; name: string; price: number }>;
}
```

`HoldLineItem.unitPrice`, currency, tier, and quantity come from SeatLayer, but
your server still re-inspects the hold before payment.

## Build the custom cart safely

<Steps>
  <Step title="Render selection">
    Derive immediate UI from `onSelectionChange`; keep stable ids separate from
    buyer labels.
  </Step>
  <Step title="Create the hold">
    Call the appropriate hold method when the buyer continues. Disable duplicate
    submissions while it runs.
  </Step>
  <Step title="Start a visible timer">
    Count down from server `expiresAt`, not a locally invented duration.
  </Step>
  <Step title="Hand off only holdId">
    Your backend inspects authoritative items, runs payment/order logic, and
    books.
  </Step>
  <Step title="Recover intentionally">
    Clear stale selection/cart on expiry, show adjacency-specific errors, and
    reuse `bookingRef` for uncertain server retries.
  </Step>
</Steps>

## Integration checklist

- [ ] The host container has a stable width and height.
- [ ] Every callback updates host state idempotently.
- [ ] `null` async results have explicit buyer copy.
- [ ] GA, tier, best-available, and partial release paths are tested if used.
- [ ] Hold time is based on `expiresAt`.
- [ ] Server checkout ignores browser prices.
- [ ] `destroy()` runs during unmount/navigation.
- [ ] Keyboard, touch, zoom, and colorblind-safe behavior pass.

Continue with [SeatPicker](/buyer-sdk/seat-picker),
[groups and GA](/integrations/groups-and-ga), or
[best available](/buyer-sdk/best-available).