Publish your free/busy availability
Share Northwind open slots without exposing meeting details — perfect for a booking page.
Published Sep 18, 2025
Booking pages are a fine idea — the problem is that the third-party ones want to read every meeting on your calendar to compute availability. For a studio like Northwind, where titles include client names and brief details, that is more data than you want to hand over. The other option is to publish your calendar publicly, which exposes every meeting subject to the world.
This script gives you a third path. Deployed as a web app, it returns a JSON list of open 30-minute slots in your working hours over the next week — slot times only, no titles, no attendees, no meeting bodies. Point a tiny booking page or a script at the URL and it has everything it needs without seeing any of the things it should not.
What you’ll need
- Access to the calendar whose availability you want to publish. The script
uses
CalendarApp.getDefaultCalendar(); swap ingetCalendarByIdfor a shared resource. - A deployment as a web app (Apps Script editor → Deploy → New deployment → Web app). Choose “Execute as: me” and “Who has access: Anyone” if you want the booking page to call it unauthenticated.
- Nothing else — no spreadsheet, no API keys.
The script
// How many days ahead to publish availability for.
const LOOKAHEAD_DAYS = 7;
// Working hours, 24-hour clock. 9am inclusive to 6pm exclusive.
const WORK_START_HOUR = 9;
const WORK_END_HOUR = 18;
// Length of each bookable slot, in minutes.
const SLOT_MINUTES = 30;
/**
* Web app entry point. Returns the next week of open working-hour slots
* as a JSON array, no event details exposed.
*/
function doGet() {
const slots = freeSlots(new Date(), LOOKAHEAD_DAYS);
return ContentService.createTextOutput(JSON.stringify(slots))
.setMimeType(ContentService.MimeType.JSON);
}
/**
* Computes 30-minute open slots in the user's working hours over the next
* `days` weekdays. Returns an array of { start, end } ISO strings.
*
* @param {Date} start - The first day to check (today is fine).
* @param {number} days - How many days forward to scan.
* @return {Array<{start: string, end: string}>}
*/
function freeSlots(start, days) {
const cal = CalendarApp.getDefaultCalendar();
const slotMs = SLOT_MINUTES * 60 * 1000;
const out = [];
for (let d = 0; d < days; d++) {
// 1. Walk forward one day at a time, snapped to midnight.
const day = new Date(start);
day.setDate(start.getDate() + d);
day.setHours(0, 0, 0, 0);
// 2. Skip weekends — Sunday (0) and Saturday (6).
if (day.getDay() === 0 || day.getDay() === 6) continue;
// 3. Step through the working hours in SLOT_MINUTES chunks.
for (let h = WORK_START_HOUR; h < WORK_END_HOUR; h++) {
const slotStart = new Date(day);
slotStart.setHours(h);
const slotEnd = new Date(slotStart.getTime() + slotMs);
// 4. A slot is free only if the calendar returns no events in it.
if (cal.getEvents(slotStart, slotEnd).length === 0) {
out.push({
start: slotStart.toISOString(),
end: slotEnd.toISOString(),
});
}
}
}
return out;
}
How it works
doGetruns whenever the deployed web app URL is hit. It callsfreeSlotsand serialises the result as JSON.freeSlotswalks the next seven days, one day at a time, snapping to midnight so the day arithmetic is consistent across daylight-saving transitions.- It skips Saturday and Sunday (
getDay()returns 6 and 0) so the booking page only offers weekday slots. - For each weekday it iterates through working hours in
SLOT_MINUTESchunks (30 minutes by default), from 9am inclusive to 6pm exclusive. - For each candidate slot, it calls
cal.getEvents(slotStart, slotEnd)and only pushes the slot onto the output array if no events overlap it. The slot is emitted as an ISO 8601 string pair — no titles, no attendees, no description.
Example run
A GET to the deployed web app URL returns something like this:
[
{ "start": "2025-09-19T09:00:00.000Z", "end": "2025-09-19T09:30:00.000Z" },
{ "start": "2025-09-19T09:30:00.000Z", "end": "2025-09-19T10:00:00.000Z" },
{ "start": "2025-09-19T14:00:00.000Z", "end": "2025-09-19T14:30:00.000Z" },
{ "start": "2025-09-22T11:00:00.000Z", "end": "2025-09-22T11:30:00.000Z" }
]
A booking page renders that as a grid of times the visitor can click. None of the busy times leak — they simply do not appear.
Run it
This is an HTTP endpoint, not a scheduled job:
- In the Apps Script editor, click Deploy → New deployment.
- Pick Web app, set “Execute as” to yourself and “Who has access” to Anyone (or Anyone with a Google account, if you want to restrict it).
- Approve the calendar authorisation prompt.
- Copy the deployment URL and call it from your booking page or test it directly in a browser.
To pick up changes after editing the script, redeploy as a new version.
Watch out for
- ISO strings are in UTC. The booking page is responsible for converting
to the visitor’s local time. If you want to publish a fixed display
timezone, format with
Utilities.formatDateand your preferred zone before serialising. - Every slot is one calendar
getEventscall. With seven days, nine working hours and 30-minute slots that is 126 API calls per request, fast enough but not instant. If the page is hit often, cache the result inCacheServicefor a few minutes. - All-day events show up as overlapping every slot of that day, which is
usually what you want for holidays. If you have an “On call” all-day
marker that should not block bookings, filter
getEventsresults by title before counting. - “Anyone” access is genuinely public. Anyone who guesses the URL can fetch your free slots. Treat the URL like an unlisted link rather than a secret — it leaks availability shape, even if it does not leak meeting details.
Related
Send a feedback survey after each event
Email attendees a survey link automatically after Northwind workshops or trainings.
Updated Oct 24, 2025
Build a team-capacity view from calendars
Show how booked the Northwind team is this week — meeting hours per person.
Updated Oct 20, 2025
Flag meetings that could have been emails
Detect short, agendaless, oversized meetings — the smell of bad calendar hygiene.
Updated Oct 12, 2025
Send tiered deadline countdown reminders
Email Northwind teammates at 7, 3, and 1 days out from a Sheet of upcoming deadlines.
Updated Sep 30, 2025
Archive past events to a log sheet
Keep a searchable Northwind meeting history — every event logged with title, attendees, duration.
Updated Sep 26, 2025