Automatically Block Time Slots in Google Calendar With Google Apps Script

In this post, I’ll walk you through creating a Google Apps Script that automatically blocks time slots in your Google Calendar based on the free/busy status of other shared calendars. This can be useful when you need to keep your calendar in sync with colleagues or team members.

Getting Started

First, create a new Google Sheet and rename it as you like. Then, open the Script Editor by going to Extensions > Apps Script. In the Script Editor, create a new script file and copy the following code into it:

function onOpen() {
  const spreadsheet = SpreadsheetApp.getActive();
  const menuItems = [
    {name: 'Block Appointments', functionName: 'run_blockAppointments'}
  ];
  spreadsheet.addMenu('Calendar Blocker', menuItems);

function run_blockAppointments() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Config");
  const localCalendarID = sheet.getRange("B1").getValue();
  const lastRow = sheet.getLastRow();
  const remoteCalendarIDs = sheet.getRange("B2:B" + lastRow).getValues().flat().filter(String);

  blockAppointments(remoteCalendarIDs, localCalendarID);
}

function blockAppointments(remoteCalendarEmails, localCalendarEmail) {
  const startDate = new Date();
  const endDate = new Date(startDate);
  endDate.setDate(startDate.getDate() + 7);

  const localCalendar = CalendarApp.getCalendarById(localCalendarEmail);
  const freebusy = Calendar.Freebusy.query({
    timeMin: startDate.toISOString(),
    timeMax: endDate.toISOString(),
    items: remoteCalendarEmails.map((email) => ({ id: email })),
  });

  const combinedBusySlots = remoteCalendarEmails.flatMap((email) => freebusy.calendars[email].busy);

  combinedBusySlots.forEach((slot) => {
    const startTime = new Date(slot.start);
    const endTime = new Date(slot.end);
    const existingEvents = localCalendar.getEvents(startTime, endTime);

    const blockingEmails = remoteCalendarEmails.filter((email) => {
      const remoteBusySlots = freebusy.calendars[email].busy;
      return remoteBusySlots.some((remoteSlot) => {
        return startTime.getTime() === new Date(remoteSlot.start).getTime() &&
          endTime.getTime() === new Date(remoteSlot.end).getTime();
      });
    });

    if (!existingEvents.length) {
      if (blockingEmails.length) {
        const blockingDomains = blockingEmails.map((email) => email.split('@')[1]).join(', ');
        const eventTitle = `Block - ${blockingDomains}`;
        localCalendar.createEvent(eventTitle, startTime, endTime);
      }
    } else {
      existingEvents.forEach((event) => {
        const isScriptCreatedEvent = remoteCalendarEmails.some((email) => {
          const domain = email.split('@')[1];
          return event.getTitle().includes(`Block - ${domain}`);
        });

        if (isScriptCreatedEvent) {
          if (blockingEmails.length) {
            const blockingDomains = blockingEmails.map((email) => email.split('@')[1]).join(', ');
            const eventTitle = `Block - ${blockingDomains}`;
            event.setTitle(eventTitle);
          } else {
            event.deleteEvent();
          }
        }
      });
    }
  });
}