Google Apps Script that syncs out-of-office events from team members' individual calendars into a shared team calendar. Based on Google's vacation calendar sample — see below for differences.
- Pulls the member list from configured Google Groups (
pde@vida.com,dev-offshore@vida.com) - Scans each member's calendar for OOO events (looking for
outOfOfficeevent types with keywords like "pto", "ooo", "out of office") - Imports matching events into the shared team calendar, prefixed with the username (e.g.
[jane.doe] OOO - Vacation) - Only syncs events that are at least 6 hours long and up to 3 months in the future
- Uses incremental sync — on subsequent runs, only checks events modified since the last run (within a 7-day window)
Runs on a time-based trigger every 5 minutes.
| File | Description |
|---|---|
Code.js |
Main sync logic — group member lookup, event filtering, and calendar import |
CalendarTrigger.js |
Experimental calendar-change triggers (not actively used) |
appsscript.json |
Apps Script project manifest (runtime config, scopes, advanced services) |
- A Google Workspace account with access to the target Google Groups
- A shared Google Calendar to import events into
- The Calendar Advanced Service enabled in the Apps Script project
In Code.js, set these constants:
TEAM_CALENDAR_ID— the ID of the shared team calendarGROUP_EMAIL— email address(es) of the Google Group(s) containing team membersKEYWORDS— event summary keywords to matchMONTHS_IN_ADVANCE— how far ahead to scan
Run the setup() function from the Apps Script editor to create the time-based trigger and run the initial sync.
This project uses clasp to sync between the local repo and Google Apps Script.
npm install -g @google/clasp
clasp login
clasp pull # download latest from Apps Script
clasp push # upload local changes to Apps Script. Immediately live!This project is based on Google's vacation-calendar sample. Key changes:
- Incremental sync guard — If
lastRunis more than 7 days old, it resets to a full sync to avoid the Calendar API's "modification time lies too far in the past" error - New-user detection — Tracks known users in script properties; when a new member joins a group, their first sync does a full scan instead of incremental
- Keyword filter in
shouldImportEvent— The original imports alloutOfOfficeevents unconditionally (theKEYWORDSconstant is defined but unused). This version actually filters events by summary keywords (pto,ooo,out of office) - Minimum duration filter — Only syncs OOO events that are at least 6 hours long (filters out short focus-time blocks, etc.)
- Attendee acceptance check — For events the user didn't create, only imports if the user has accepted the invite
delete event.sequenceinimportEvent— Strips the sequence number before importing to avoid conflicts with the target calendar- Removed
ONLY_DIRECT_MEMBERSflag — The sample supports toggling between direct/indirect member lookup; this version always uses indirect (recursive) lookup - Explicit OAuth scopes and Calendar advanced service — The sample's manifest has no scopes or advanced services declared; this version pins them in
appsscript.json - Error handling in
getUsersFromGroups— Wraps each group lookup in try/catch so one inaccessible group doesn't break the entire sync findEventserror handling — Changedcontinuetobreakon API errors to prevent an infinite retry loop when pagination failsCalendarTrigger.js— Experimental code for per-user calendar change triggers (not used in production due to trigger limits)
calendar.readonly— read team members' calendarscalendar— write events to the shared calendarscript.scriptapp— manage triggersgroups— read Google Group membership