appscript.dev
Automation Intermediate Calendar

Auto-block focus time around meetings

Reserve prep and recovery slots automatically around every Northwind meeting.

Published Jun 30, 2025

Back-to-back meetings leave no room to prepare for the next one or to write up the last. At Northwind, the fix is a discipline nobody keeps up by hand: blocking fifteen minutes before each meeting to get your head in, and fifteen after to capture actions before they evaporate. Add it manually and you forget; skip it and your day becomes a wall of calls.

This automation looks ahead seven days and, for every real meeting on your calendar, drops a short “Focus — prep” block before it and a “Focus — recovery” block after it. It tags each meeting once it has been processed, so running it again never doubles up the blocks.

What you’ll need

  • Access to your default Google Calendar — the script uses CalendarApp.getDefaultCalendar(), so there is nothing to configure beyond the two duration constants.
  • That is it. The script creates and tags its own events.

The script

// Minutes of prep time to reserve before each meeting.
const PREP_MINUTES = 15;

// Minutes of recovery time to reserve after each meeting.
const RECOVERY_MINUTES = 15;

// How many days ahead to scan for meetings.
const LOOKAHEAD_DAYS = 7;

// Tag key used to mark a meeting as already processed.
const PROCESSED_TAG = 'focus-blocked';

/**
 * Scans the next week of calendar events and reserves a prep block before
 * and a recovery block after every meeting that has not been processed yet.
 */
function blockFocusTime() {
  // 1. Define the window: now through LOOKAHEAD_DAYS from now.
  const start = new Date();
  const end = new Date(start.getTime() + LOOKAHEAD_DAYS * 86400000);
  const cal = CalendarApp.getDefaultCalendar();
  const events = cal.getEvents(start, end);

  if (!events.length) {
    Logger.log('No events in the next ' + LOOKAHEAD_DAYS + ' days.');
    return;
  }

  let blocked = 0;
  for (const event of events) {
    // 2. Skip the focus blocks themselves so we never block our blocks.
    if (event.getTitle().startsWith('Focus')) continue;

    // 3. Skip meetings already processed on a previous run.
    if (event.getTag(PROCESSED_TAG) === 'yes') continue;

    // 4. Create the prep block immediately before the meeting.
    cal.createEvent('Focus — prep',
      new Date(event.getStartTime().getTime() - PREP_MINUTES * 60000),
      event.getStartTime(),
      { description: 'Prep for ' + event.getTitle() });

    // 5. Create the recovery block immediately after the meeting.
    cal.createEvent('Focus — recovery',
      event.getEndTime(),
      new Date(event.getEndTime().getTime() + RECOVERY_MINUTES * 60000),
      { description: 'Recovery after ' + event.getTitle() });

    // 6. Tag the meeting so the next run leaves it alone.
    event.setTag(PROCESSED_TAG, 'yes');
    blocked++;
  }

  Logger.log('Added focus blocks around ' + blocked + ' meetings.');
}

How it works

  1. blockFocusTime builds a time window from now to seven days ahead and fetches every event in it from the default calendar.
  2. If the window is empty, it logs a message and stops.
  3. For each event it skips anything whose title starts with “Focus” — these are the blocks the script itself created, and processing them would spiral.
  4. It skips any meeting already carrying the focus-blocked tag, which is what makes repeat runs safe.
  5. It creates a “Focus — prep” event ending exactly when the meeting starts, PREP_MINUTES long, with a description naming the meeting.
  6. It creates a “Focus — recovery” event starting exactly when the meeting ends, RECOVERY_MINUTES long.
  7. It tags the meeting with focus-blocked: yes so the next run ignores it, and logs how many meetings were handled.

Example run

Your calendar for tomorrow holds one untagged meeting:

EventStartEnd
Client review — Acme14:0015:00

After a run, two new events bracket it:

EventStartEnd
Focus — prep13:4514:00
Client review — Acme14:0015:00
Focus — recovery15:0015:15

Run the script again and nothing changes — the meeting now carries the focus-blocked tag, so it is skipped.

Trigger it

This works best running quietly each morning so new meetings get bracketed soon after they appear:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add Trigger.
  3. Choose blockFocusTime, event source Time-driven, type Day timer, and pick an early-morning hour.

Watch out for

  • The script does not check whether a slot is already busy. If a meeting starts right after another, the prep block will overlap the earlier meeting rather than find free time.
  • Tags travel with the meeting. If you reschedule a tagged meeting, its old prep and recovery blocks stay where they were and no new ones are made — delete the stale blocks or clear the tag to re-process.
  • Cancelled or deleted meetings leave their focus blocks behind. Nothing cleans them up automatically.
  • Only the default calendar is scanned. Meetings on a secondary or shared calendar are ignored unless you point CalendarApp at that calendar.
  • All-day events have a start and end too, so an all-day item will pick up midnight-adjacent focus blocks. Add a check for isAllDayEvent() if that is a problem.

Related