<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kayabyte</title>
    <description>The latest articles on DEV Community by Kayabyte (@kayabyte).</description>
    <link>https://dev.to/kayabyte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189978%2F73e94e71-0316-4cd6-be04-aba1861ba697.png</url>
      <title>DEV Community: Kayabyte</title>
      <link>https://dev.to/kayabyte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kayabyte"/>
    <language>en</language>
    <item>
      <title>Room types, not rooms: how a small B&amp;B booking engine avoids double bookings</title>
      <dc:creator>Kayabyte</dc:creator>
      <pubDate>Thu, 22 Oct 2026 08:06:31 +0000</pubDate>
      <link>https://dev.to/kayabyte/room-types-not-rooms-how-a-small-bb-booking-engine-avoids-double-bookings-1ab5</link>
      <guid>https://dev.to/kayabyte/room-types-not-rooms-how-a-small-bb-booking-engine-avoids-double-bookings-1ab5</guid>
      <description>&lt;p&gt;Full disclosure up front: I build Wix apps, and Stay Direct, the booking app this post is based on, is mine. The booking logic below works the same on any platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference between a cabin and a B&amp;amp;B
&lt;/h2&gt;

&lt;p&gt;A holiday cabin is one unit: it is either free on a night or it isn't. A small B&amp;amp;B or guesthouse sells room types instead: "Double room with garden view, 4 rooms". Guests don't pick room 3, they pick the type, and the owner wants the calendar to keep selling until every room of that type is taken.&lt;/p&gt;

&lt;p&gt;That sounds like a simple counter, but one rule makes it harder: a guest keeps the same room for the whole stay. Nobody wants to move rooms on night three.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can use before building anything
&lt;/h2&gt;

&lt;p&gt;If you are on Wix, check the built-in routes first. Wix Hotels by HotelRunner is Wix's own hotel app; it handles room types and comes with a channel manager, which suits a hotel that already works that way. Wix Bookings is made for appointments and classes, so nightly stays across several rooms don't map onto it well. And plenty of small places simply link a "Book now" button to their Airbnb or Booking.com page, which costs nothing but keeps paying the commission.&lt;/p&gt;

&lt;p&gt;What follows is for the case in between: a few rooms, direct bookings on your own site, and calendars that stay in sync with the big platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free rooms, not free nights
&lt;/h2&gt;

&lt;p&gt;So the check is not "is there one free room each night" but "is there one room that is free on every night of the stay":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// blocks: bookings, holds and imported calendar blocks; unit = room number, or none for "all rooms"&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;freeRooms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;checkOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overlapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;checkOut&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;checkIn&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;overlapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request for two rooms needs two entries in that list. And there is a third answer besides yes and no: every night has a free room, but never the same one for the whole stay. Telling the guest exactly that ("try different dates or two shorter stays") is better than a plain "unavailable".&lt;/p&gt;

&lt;h2&gt;
  
  
  Calendars per room
&lt;/h2&gt;

&lt;p&gt;Most small places also list on Airbnb or Booking.com, which sync through iCal links. With room types, each physical room needs its own export link and its own imported calendars, because Booking.com may sell room 2 while Airbnb sells room 4. An imported block takes one room; a block the owner adds by hand ("closed for painting") can take all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate options
&lt;/h2&gt;

&lt;p&gt;B&amp;amp;Bs rarely have one price. The usual set is a standard rate, a cheaper non-refundable rate and a rate with breakfast. Modelling them as options on top of the nightly price keeps it simple: a percentage up or down per night, an optional amount per guest per night (breakfast), and its own deposit rule (a non-refundable rate is often paid in full at booking).&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping old bookings safe
&lt;/h2&gt;

&lt;p&gt;The tricky part of adding rooms to an existing booking engine is the data that was there before. Old bookings have no room number. The safe default is "room 1": for a property with one room nothing changes, and when an owner later raises the room count, old bookings keep occupying exactly one room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;For multi-room properties: book room types, assign a concrete room for the whole stay, explain the "same room" case clearly, keep one calendar link per room, and treat old bookings as room 1.&lt;/p&gt;

&lt;p&gt;If you run a B&amp;amp;B, guesthouse or holiday rental on Wix, Stay Direct does this on your own site, with iCal sync to Airbnb, Booking.com and Vrbo and deposits through your checkout, and a free plan for one property (online payments need a Wix Premium plan and a connected payment provider). It only works on Wix sites, and it syncs booked dates, not prices.&lt;/p&gt;

&lt;p&gt;Stay Direct on the App Market: &lt;a href="https://www.wix.com/app-market/6de5633d-3a41-473c-a642-20b4ba638d1d" rel="noopener noreferrer"&gt;https://www.wix.com/app-market/6de5633d-3a41-473c-a642-20b4ba638d1d&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>wix</category>
      <category>travel</category>
    </item>
    <item>
      <title>Booking for kennels and dog daycares is a capacity problem, not a calendar problem</title>
      <dc:creator>Kayabyte</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:32:31 +0000</pubDate>
      <link>https://dev.to/kayabyte/booking-for-kennels-and-dog-daycares-is-a-capacity-problem-not-a-calendar-problem-17p2</link>
      <guid>https://dev.to/kayabyte/booking-for-kennels-and-dog-daycares-is-a-capacity-problem-not-a-calendar-problem-17p2</guid>
      <description>&lt;p&gt;Full disclosure up front: I build Wix apps, and the one this post is about, Pet Boarding, is mine. Most of this post is about the booking logic itself, which applies whatever platform you build on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why appointment tools don't fit
&lt;/h2&gt;

&lt;p&gt;Most booking tools are built around appointments: one client, one staff member, one time slot. A kennel or a dog daycare works differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A boarding stay is a range of nights, from a drop-off day to a pick-up day.&lt;/li&gt;
&lt;li&gt;Daycare is a set of single days, sometimes half days.&lt;/li&gt;
&lt;li&gt;What runs out is space, not staff time. A kennel with 12 runs can take 12 dogs per night, whoever books them.&lt;/li&gt;
&lt;li&gt;Price depends on the pet, not just the service: size tiers, a discount for a second dog from the same family, weekend or holiday extras, a late pick-up fee.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the core question for every request is "is there a free space on every night of this stay", and the price is a sum over nights and pets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capacity per night
&lt;/h2&gt;

&lt;p&gt;The availability check counts how many spaces are taken on each night of the requested stay and compares that with the service's capacity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// taken[night] = spaces used that night by confirmed bookings and live holds&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stayNights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;petsNeeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stayNights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;night&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;night&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;petsNeeded&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Family suites.&lt;/strong&gt; Many kennels put dogs from one family in one run. With suites on, a family of two dogs uses one space, so &lt;code&gt;petsNeeded&lt;/code&gt; becomes the number of suites, not the number of pets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The calendar should only offer dates that work.&lt;/strong&gt; The widget asks the backend for free spaces per night in the visible months and greys out days where the stay could not start, so owners don't pick dates and then get an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Holds, because payment takes time
&lt;/h2&gt;

&lt;p&gt;Between "Continue" and a finished payment, two owners can try to take the last space. A short hold solves that: when a checkout starts, the space is held for the stay's nights, and the hold expires if nobody pays. Capacity is checked again when the hold is written, and simultaneous requests for the last space get a clear winner, so two owners can't both take it.&lt;/p&gt;

&lt;p&gt;Manual payments (cash or bank transfer at drop-off) need a longer hold, because the order exists but the money comes later. In the app, online checkouts hold the space for 30 minutes and pending manual payments for 3 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prices per pet
&lt;/h2&gt;

&lt;p&gt;The quote is built night by night and pet by pet, then adjusted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;base price per night (boarding) or per day (daycare, with a half-day price),&lt;/li&gt;
&lt;li&gt;a size tier per pet,&lt;/li&gt;
&lt;li&gt;an extra-pet discount for the second and later pets of the same owner,&lt;/li&gt;
&lt;li&gt;weekend and peak-date extras,&lt;/li&gt;
&lt;li&gt;a long-stay discount above a number of nights,&lt;/li&gt;
&lt;li&gt;a late pick-up fee when the pick-up time is after the cut-off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rounding happens in the currency's smallest step, so a 10% discount on a price in yen or Indonesian rupiah doesn't produce fractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Records that have to stay private
&lt;/h2&gt;

&lt;p&gt;Owners upload vaccination records (PDF or a photo). These are health-related documents about someone's pet and contact details about the owner, so they are stored per site, served only to that site's dashboard, and size-capped (5 MB per file). Old owner and pet details are cleared after a retention period.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the business sees
&lt;/h2&gt;

&lt;p&gt;The part kennel owners care about most is not the booking form but the day: who is dropping off today, who is picking up, which pets are in care and any care notes. A printable day sheet, an occupancy calendar and a form for bookings taken by phone matter more to them than any widget design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If you build booking for boarding or daycare, model capacity per night (or per day), make holds part of the capacity check, price per pet, and treat uploaded records as private data from the start.&lt;/p&gt;

&lt;p&gt;If you run a kennel, cattery or dog daycare on Wix, Pet Boarding does this on your own site, with deposits through your checkout and a free plan for one service. It only works on Wix sites. More about it: &lt;a href="https://kayabyte.dev/wix/pet-boarding/" rel="noopener noreferrer"&gt;https://kayabyte.dev/wix/pet-boarding/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>wix</category>
      <category>saas</category>
    </item>
    <item>
      <title>Wix Events has no total ticket limit per event. Here's what works, and how I built one</title>
      <dc:creator>Kayabyte</dc:creator>
      <pubDate>Wed, 21 Oct 2026 23:14:26 +0000</pubDate>
      <link>https://dev.to/kayabyte/wix-events-has-no-total-ticket-limit-per-event-heres-what-works-and-how-i-built-one-396c</link>
      <guid>https://dev.to/kayabyte/wix-events-has-no-total-ticket-limit-per-event-heres-what-works-and-how-i-built-one-396c</guid>
      <description>&lt;p&gt;Full disclosure up front: I build Wix apps, and one of them, Event Capacity &amp;amp; Waitlist, does what this post is about. I'll cover the built-in options first, because for many events they are enough, and then the technical side of how a shared limit can be built on top of the Wix Events APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Wix Events sets ticket limits per ticket type, not per event. Wix says it directly in a feature request: "You cannot set a general ticket limit of how many tickets you can sell per event."&lt;/p&gt;

&lt;p&gt;A typical case: a room for 40 people, with "Standard" and "VIP" tickets. You can't give each type 20, because you don't know the split. You can't give each type 40, because then you might sell 80.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in option 1: one ticket type with price options
&lt;/h2&gt;

&lt;p&gt;If your ticket types only differ in price (adult, child, member), use one ticket type with several price options and set its quantity to the room size. All prices then sell from one pool.&lt;/p&gt;

&lt;p&gt;The catch: price options share one name, one description and one sale period. If VIP needs its own description, or early-bird tickets should stop on a date, they have to be separate ticket types again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in option 2: a seating map
&lt;/h2&gt;

&lt;p&gt;A seating map caps the event at the seats and areas on the map. An "Area" element has a total number of spots, which works for standing room. But each seat or area belongs to one ticket type, so two types can't share the same free places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in option 3: RSVP events
&lt;/h2&gt;

&lt;p&gt;RSVP events (no payment) have a guest limit and a built-in waitlist. Ticketed events don't: Wix's help article says "Waitlists are not available for ticketed events", and an event can't be switched from ticketed to RSVP later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a shared limit on top of the APIs
&lt;/h2&gt;

&lt;p&gt;Wix has no "event capacity" field to set, so the app works with what exists: each ticket definition's own limit, and the event's tickets-per-order setting. The idea is simple. After every order, recalculate each ticket type's limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// remaining = total capacity minus everything sold or held, across all types&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sold&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sold&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                          &lt;span class="c1"&gt;// what it may sell now&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ownerLimit&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ownerLimit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setTicketLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;     &lt;span class="c1"&gt;// never below what's sold&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things that turned out to matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remember the owner's own limits.&lt;/strong&gt; If the owner set VIP to 20, the app must never raise VIP above 20, and it must put 20 back when the shared limit is switched off. The app stores what it wrote, so it can tell its own values apart from limits the owner changed later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed orders.&lt;/strong&gt; Per-type limits alone let one order take 4 Standard plus 4 VIP when only 4 places are left. The event's "ticket limit per order" counts across types, so the app keeps it at or below the places left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Held tickets count.&lt;/strong&gt; Tickets in an open checkout are reserved, and they need to count as used, or two buyers can check out the same last place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent writes.&lt;/strong&gt; A sale and a recalculation can touch the same ticket definition at once. Updates carry a revision, and a conflict means: read everything again and retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sold-out events get no more order events.&lt;/strong&gt; If a recalculation is interrupted (for example, the owner leaves the page right after saving), nothing triggers the next one. The settings card now checks on open whether the stored limits match the settings, and finishes the update if not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's still a small window between an order and the recalculation, so two checkouts started at exactly the same moment could, in rare cases, go one over.&lt;/p&gt;

&lt;h2&gt;
  
  
  A waitlist for a ticketed event
&lt;/h2&gt;

&lt;p&gt;When the shared total is used up, the app shows a "Join the waitlist" form on the event page (a site plugin on the event details page). When places free up, because an order is cancelled or the owner raises the total, it fires a Wix Automations trigger, so the site's own email goes to the next people in line. It doesn't send email itself, and it doesn't reserve the ticket: the person buys it the normal way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ticket types that differ only in price: use price options. Free, and one pool.&lt;/li&gt;
&lt;li&gt;Fixed seats: a seating map.&lt;/li&gt;
&lt;li&gt;No payment needed: an RSVP event with its built-in waitlist.&lt;/li&gt;
&lt;li&gt;Different ticket types sharing one total, or a waitlist for a paid event: that needs an app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you go the app route, Event Capacity &amp;amp; Waitlist is on the Wix App Market, with a free plan for one event. I wrote a longer, non-technical walkthrough of the built-in options here: &lt;a href="https://kayabyte.dev/guides/wix-events-total-ticket-limit-waitlist.html" rel="noopener noreferrer"&gt;https://kayabyte.dev/guides/wix-events-total-ticket-limit-waitlist.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions about the APIs or the approach are welcome in the comments.&lt;/p&gt;

</description>
      <category>wix</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>events</category>
    </item>
  </channel>
</rss>
