Appearance
Google Calendar sync for bookings in Laravel, without OAuth screens
Ask a salon what their booking page must do and the first answer is not the booking page. It is the stylist's phone: her appointments live in Google Calendar, with her dentist and her daughter's school run, and the site has to know all of it. A booking the site takes over her day off is a client sent home. A booking she takes at the desk and never types into the site is a slot the site sells twice.
Every Laravel booking site solves this again. The Google Calendar API is documented; what takes the week is everything around it — OAuth consent screens for a client who is not a developer, refresh tokens that die after seven days while the consent screen sits in Testing, a sync that must run every few minutes without reading the stylist's private titles, and the one booking that lands exactly while the sync is sleeping. kitloom/booking-calendars is that week done once, as a module of kitloom/bookings.
What the salon gets
Each staff member connects one calendar. From then on:
- Bookings go in. A waiting or confirmed booking is an event in their calendar — Haircut — Anna K., the phone and the note in the description, tentative until they confirm. Moved by the front desk, it moves; given to a colleague, it changes calendars; cancelled, it is gone.
- Busy comes back. What is busy in the calendar — any event, whatever it is — is not offered on the booking form, to clients and to the desk alike. Only the times are read; the titles stay with the staff member.
- The moment of booking is checked. A sync every five minutes is not enough for the appointment the stylist typed a minute ago: inside the booking, the site looks at the calendar once more, three seconds at most, and falls back on the last reading.
Nothing of this reaches a client. A calendar that fails lands on its row in the admin with Google's words, Site Health warns, a Telegram channel can be told, and the booking is taken all the same.
Set up in the admin, no sign-in
The road most tutorials take is OAuth: a Cloud project, a consent screen, every staff member clicking Allow in a popup, and a refresh token to keep alive. kitloom takes the other road: one service account for the site, and each staff member shares their calendar with it.
- In Google Cloud: a project, the Google Calendar API enabled, a service account under Credentials, its JSON key downloaded. Ten minutes, once, no consent screen, nothing to publish.
- Booking Calendars → Settings: paste the key file. The page shows the account's address,
booking-calendars@…iam.gserviceaccount.com, and keeps the key encrypted. - Each staff member, in their Google Calendar: Settings and sharing → Share with specific people → that address, Make changes to events. For a second calendar, its id from Integrate calendar.
- Booking Calendars → Calendars → New: the staff member and the address. Saving looks at the calendar through Google at once — Connected, or Not shared with the address to share it with — reads it, and sends the staff member's upcoming bookings into it.
Nothing expires. Nobody signs in anywhere. A new staff member is one sharing step.
The parts you would otherwise build
- No SDK, no tokens to keep. Google is called over Laravel's HTTP client with a JWT signed by the key; the access token lives in the cache for its hour.
- Pushes from the queue, three tries. A slow answer from Google never slows a booking; a failure counts on the calendar's row, and five in a row stop pushing to it until a call gets through.
- Busy times through a contract. The module hands its stretches to kitloom/bookings through
BusyTimes, the same door a Microsoft 365 module or a staff member's second job would use. - An event deleted by hand comes back. The client was promised the time; freeing it is a cancellation on the site, not a deletion in Google.
- Privacy follows the data. The events carry the client's name into Google; Erase Personal Data rewrites them to name nobody.
Run it from an AI agent
The calendars are open to an agent connected through kitloom/ai, as the user whose key it holds:
- "Why isn't Tuesday's booking in Mira's calendar?" —
bookings_calendars_listanswers whose calendar is not shared and what Google said. - "Mira shared her calendar, check it again." —
bookings_calendars_syncreads it now and reports the status.
Connecting a calendar and saving the key stay in the admin: the key is a secret, and secrets stay out of chats.
Try it
Open a sandbox in one click, with no sign-up: Filament or Nova. Every detail of the module is in the Booking Calendars guide.