Skip to content
Docs
BlueprintPrototype

Add a seat map to your site

Sell reserved seats on your own website: put an event on a venue's seating plan, let the buy modal show its Seats step, or place the seat explorer and the cart on your page yourself, in your brand's colours.

In short. Pin a venue's live seating plan to an event, then sell its seats on your site through the buy modal or the seat explorer beside the cart.

When to use this#

Use this when

Your event is seated: a theatre, a concert hall, an arena with numbered seats, and buyers should choose where they sit.

You sell on your own website, with the Elements, and want the seat map in your brand.

Use something else when

The event is general admission: a ticket type with a capacity is all it needs, and the buy modal shows Tickets.

You sell on Live Storefront only: it shows the same seat map without any code.

What you'll build#

  1. Venue planThe room, drawn once, marked live
  2. Event planCategories per seat, production kills
  3. Your siteAn allowed origin on the channel
  4. Seat mapThe buy modal, or the explorer and the cart
  5. They buyTap a seat, choose the type, pay

What the buyer gets#

Tapping a seat reserves it at once, on its category's standard ticket type, and pins a card beside it. There is no step in between: the seat is in the cart, held for the buyer until the cart expires or they pay. Tapping it again gives it back.

A seat held in the stalls centre: the card beside it shows the view from the seat, Row D Seat 9, Premium, Held for you, and Adult and Child with their prices.A seat held in the stalls centre: the card beside it shows the view from the seat, Row D Seat 9, Premium, Held for you, and Adult and Child with their prices.
A held seat and its card: the view from the seat, the price including fees, and the ticket type to change.
  • The card, tl-seat-card, says which seat it is (row, seat, section and price category), its price including fees, and draws the view from the seat. A tap on the picture opens the view larger.
  • Changing the type is the card's tl-price-picker: the ticket types the seat's category is priced for, Adult or Child for example. Changing it keeps the seat: the reservation stays and only the price moves. The cart offers the same choice per seat line.
  • The key, tl-map-key, shows each price category's colour and lowest price, and a minimap of the venue to choose a section from.
  • The venue in 3D, when the venue has a 3D model: a toggle beside the zoom buttons shows the levels, flies into a section and puts every seat where it really is, in the flat map's colours. A tap there holds a seat exactly as on the flat map. The model is loaded only when the buyer presses the toggle, so a buyer who stays on the flat map downloads none of it, and a browser that cannot draw 3D keeps the flat map without the toggle.
  • How many tickets is asked in the buy modal, beside the map, with suggestions for that many seats together; on your own page the buyer taps as many seats as they want, up to the channel's limit per order.
  • A seat that has just gone is refused with the nearest free seat offered instead.
  • A single empty seat left between two taken seats, or against the end of a row, is not allowed online: the map says so and offers the same number of seats shifted along, so nobody strands a seat that cannot be sold.
  • Without a pointer, the arrow keys move between sections and then between seats, Enter takes one and Escape steps back. Every hold, release and lost seat is announced, and a list of each section's seats is the alternative to the map. A seat's state is never told by colour alone.

Before you start#

  • An organisation in test mode. Everything here is safe to try there.
  • A venue with a seating plan whose version is live. Someone with permission to manage the venue publishes a version and marks it live (Make a basic seating map has the clicks).
  • An event at that venue with ticket types priced per category, listed on a website sales channel with a publishable key.
  • Your site already loads the Elements: the HTML quickstart, the Vite quickstart or the Next.js quickstart.

Steps#

Five steps. Each one works in Backstage or with the API and the Elements.

Put the event on the seating plan#

An event plan pins one live version of the venue's seating plan and lays the event over it: which price category each seat is in, and which seats production kills. The venue's plan is not copied, so a change for this event never moves a seat for another.

In Backstage

Open Events, then the event, then Seating. Choose the venue's live seating plan, add the price categories with their colours, paint the seats into them, and kill any seats the production needs. Price each category on the event's Tickets page. A tapped seat takes the first ticket type on sale for its category on the channel, in the order of the event's ticket types. Make a basic seating map walks through it.

Events / The Importance of Being Earnest /Seating
Seating planThe Ticketlayer Playhouse, End stage, version 3Live
PremiumPriced on the Tickets page195 seats
StandardPriced on the Tickets page737 seats
Killed for the sound deskStalls Centre, row S8
With the API

Create the plan on a live venue plan version, then assign seats by their index in that version. A category's defaultTicketTypeId is the type a tapped seat takes; left out, it is the first ticket type priced for the category on the channel.

event-plan.sh
curl -X POST https://api.ticketlayer.com/v1/events/evt_.../event-plan \
  -H "Authorization: Bearer tlak_..." \
  -H "X-Ticketlayer-Org: your-org-slug" \
  -H "Content-Type: application/json" \
  -d '{ "venuePlanVersionId": "vpv_...",
        "categories": [
          { "name": "Premium", "color": "#7c3aed", "defaultTicketTypeId": "tt_..." },
          { "name": "Standard", "color": "#0ea5e9" } ] }'

curl -X PUT https://api.ticketlayer.com/v1/events/evt_.../event-plan/seat-categories \
  -H "Authorization: Bearer tlak_..." \
  -H "X-Ticketlayer-Org: your-org-slug" \
  -H "Content-Type: application/json" \
  -d '{ "assignments": [ { "categoryId": "cat_...", "seats": [120, 121, 122] } ] }'

Let your site load the map#

The seat map is read from Ticketlayer's edge with a short-lived token that Live mints only for a page on one of the channel's allowed origins. Add every origin your site serves from.

In Backstage

Open Sales channels, then the website channel, and add your site to its Allowed origins, for example https://tickets.example.com.

With the API

The channel's customerFacing.allowedOrigins. A page on any other origin gets ORIGIN_NOT_ALLOWED.

allowed-origins.sh
curl -X PATCH https://api.ticketlayer.com/v1/sales-channels/ch_... \
  -H "Authorization: Bearer tlak_..." \
  -H "X-Ticketlayer-Org: your-org-slug" \
  -H "Content-Type: application/json" \
  -d '{ "customerFacing": { "allowedOrigins": ["https://tickets.example.com"] } }'

Let the buy modal show its Seats step#

The quickest seat map is the one you already have. The buy modal asks whether the date the buyer picks is seated, and if it is, shows Seats where it would show Tickets: the map across the sheet, and beside it "How many tickets?" with the best suggestions for that many.

In Backstage

Nothing to switch on. A date of an event with an event plan is seated, so its buy button opens on the map; a general admission date of the same event keeps the Tickets step.

With the API

The buy button from the quickstart opens the buy modal for its event. The demo night below opens the same modal.

buy-button.html
<tl-buy-tickets-button event-id="evt_..." size="lg"></tl-buy-tickets-button>
The buy modal's Seats step: the seat map beside How many tickets?, under the event's name and date.The buy modal's Seats step: the seat map beside How many tickets?, under the event's name and date.
The buy modal on the demo night: the Seats step, then the basket. The demo stops before payment.

Or place the seat explorer yourself#

For a seat map on the page rather than in a modal, place tl-seat-explorer with tl-cart beside it. They share the cart: a seat tapped on the map is a line in the cart, a type changed in the cart changes on the card, and a seat given back in either is gone from both.

In Backstage

Copy the ids the elements need: the event's id from Events, then the event, and the date's id from the event's Occurrences.

With the API

The explorer fills the box you give it, so give it a height. It starts on the flat map; dimension="3d" starts it in the venue in 3D where the venue has a model, and on the flat map where it has none. tlSeatSelect says a seat was held or given back, by its index in the venue plan version and its section; tlCartChange lists every seat held for the date. Never store the index as a seat's identity: the cart line carries the label, section, row and number.

With the loader script from the HTML quickstart in the page's head:

seats.html
<div class="seats">
  <tl-seat-explorer event-id="evt_..." occurrence-id="eo_..."></tl-seat-explorer>
  <tl-cart></tl-cart>
</div>

<style>
  .seats { display: grid; grid-template-columns: 1fr 320px; gap: 1rem; }
  .seats tl-seat-explorer { height: 560px; }
  @media (max-width: 720px) { .seats { grid-template-columns: 1fr; } }
</style>

<script>
  const map = document.querySelector('tl-seat-explorer');
  map.addEventListener('tlSeatSelect', (e) => {
    const { seat, sectionId, selected } = e.detail;
    console.log(selected ? 'held' : 'given back', seat, sectionId);
  });
  map.addEventListener('tlCartChange', (e) => {
    document.title = `${e.detail.seats.length} seats held`;
  });
</script>
The seat explorer beside the cart: two seats held in the stalls, each a line in the cart with its ticket type and price.The seat explorer beside the cart: two seats held in the stalls, each a line in the cart with its ticket type and price.
The explorer and the cart on the demo night. Fly into a section, tap a seat, change its type in the cart.

Match your brand#

The map takes its colours from the theme tokens. The seats' price category colours are the event plan's; everything else on the map is a token.

In Backstage

Set the brand colour in the channel's theme and it reaches the map on its own: a held seat, its ring and an available seat with no category colour are drawn in it. The category colours are set on the event's Seating.

With the API

The map's own tokens are --tl-map-* (the background, the plates, the sections, the labels and the price pins), --tl-stage-* and --tl-seat-* (each state, the held seat and the sizes seats are drawn at); the Elements reference lists them all. The map reads them once, when it first draws, so set them in a stylesheet rather than from a script afterwards. They reach the explorer inside the buy modal too.

seat-map-theme.css
:root {
  --tl-map-background: #f7f5f2;
  --tl-map-section: #ebe7e1;
  --tl-stage-fill: #1f2937;
  --tl-stage-label: #f9fafb;
  --tl-seat-unavailable: #d6d3d1;
  --tl-seat-selected: #b91c1c;
  --tl-seat-selected-ring: #111827;
  --tl-map-pin-active: #b91c1c;
  --tl-seat-scale: 0.45;
}

Check it in test mode#

Test mode

Open your page on an allowed origin, open the seated date and tap a seat: it turns into a held seat with its card, and the cart shows a line with the seat's row and number on the standard ticket type. Change the type, give the seat back, then try to leave a single empty seat between two held ones: the map refuses and offers a shifted pair. Pay with a test card and the order carries the seats.

Go live#

Take the organisation out of test mode, then check the channel's allowed origins list your production site and nothing you no longer serve.

Moving from tl-seat-map#

tl-seat-explorer replaced tl-seat-map, which framed the hosted box office's map, and tl-seat-map is no longer in the Elements: on a page that loads a current version, a <tl-seat-map> tag draws nothing. The explorer draws the map itself, needs no frame and reserves on a tap. To move an embed:

  • Rename the tag to tl-seat-explorer (TlSeatMap to TlSeatExplorer in React), keep occurrence-id and add event-id, which the prices come from.
  • Drop api-url, hbo-url, width and height: the explorer reads Live through the page's client, and fills the box its stylesheet gives it.
  • tlSeatAdded and tlSeatRemoved become one event, tlSeatSelect, with selected true or false.
  • tlCartChange carries seats, indexes in the venue plan version, where it carried seatIds.
  • Code that held seats through the cart manager's addSeat and removeSeat calls reserveSeat and releaseSeat instead, with the seat's index in the venue plan version.

Troubleshooting#

  • The buy modal shows Tickets, not Seats: the date's event has no event plan, or the event plan's venue plan version is not one the date sells from. Check the event's Seating.
  • The map does not load and the console shows ORIGIN_NOT_ALLOWED: the page's origin is not on the channel (step 2). The origin includes the scheme and any port.
  • "You can hold up to 10 seats in one order": the channel's seat limit per order for a date, 10 unless the channel's selling rules set another, and never more than 50.
  • A seat is refused straight after it looked free: another buyer held it first. The map refreshes within a couple of seconds and offers the nearest free seat.