A highly robust, privacy-focused Google Apps Script that automatically syncs and consolidates events from multiple source calendars (e.g., Work, Personal, School) into a single "Availability" calendar.
Keep your scheduling systems (like Calendly, Cal.com, or HubSpot) and colleagues perfectly informed of your busy times without exposing any private appointment details.
Click the button below to view a live demonstration of the synchronized public calendar:
- โจ Key Features
- ๐ How It Works (Sync Pipeline)
- ๐ Setup Instructions
- โ๏ธ Configuration Reference
- ๐ Sharing Your Availability
- ๐ ๏ธ Troubleshooting & Google Quotas
- ๐งน Clean Teardown / Uninstallation
- ๐ License
- ๐ Complete Privacy: Replaces original titles, description notes, and guest lists with custom, generic availability blocks (e.g., "Personal", "Work", "Class") so no private details leak.
- ๐งฉ Smart Overlap Consolidation: Automatically merges overlapping or adjacent events from multiple source calendars into a single, clean block. If a "Work" and "Personal" event overlap, the script titles the destination event
Work & Personaland details the active sources in the description. - ๐
Daily All-Day Splitting: Automatically converts all-day events and multi-day timed events into individual daily timed segments (
00:00:00to23:59:59). This forces calendar platforms (like Calendly and Cal.com) to block off the entire hourly grid as busy, rather than leaving them as top-banner "all-day" events which are frequently ignored. - ๐ก๏ธ Smart Filtering: Automatically ignores invitations you have explicitly declined (
GuestStatus.NO) or events marked as "Free/Transparent" (such as automatic holiday calendars, birthdays, or casual notes), ensuring only real commitments block your schedule. - ๐ Robust Reconciliation: Uses precise millisecond epoch value checking and metadata tracking saved within event descriptions to dynamically match events. It detects changes instantly and cleanly wipes out orphaned entries when a source event is deleted or moved.
- โณ Exponential Backoff: Built-in rate-limit protection automatically retries calendar modifications with randomized jitter when Google's API limits are encountered, optimizing execution speed to under 2 seconds.
- ๐ Dynamic Timezone Awareness: Automatically reads and matches your primary calendar's IANA timezone settings for perfect date boundary transitions.
The script operates on a robust, state-based reconciliation model rather than simple event copying. This ensures that deletions, modifications, and overlapping slots are dynamically recalculated on every execution.
graph TD
A[๐ Time-Driven Trigger] --> B[๐ฅ Fetch Source Events]
B --> C[๐ก๏ธ Smart Filtering]
C -->|Exclude Declined & Free Times| D[๐ Segment Splitting]
D -->|Split Multi-Day / All-Day Timed Blocks| E[๐งฉ Overlap Consolidation]
E -->|Combine Adjacent Slots & Merge Titles| F[๐ Destination Diffing]
F -->|Match via Milliseconds & Description Tags| G[โ๏ธ Reconciliation]
G -->|Create New Slots| H[๐ Destination Calendar]
G -->|Update Meta / Titles| H
G -->|Delete Orphaned / Outdated Slots| H
H --> I[๐ Sync Complete]
Warning
GitHub Security Warning: Your configured Code.gs file will contain your private Google Calendar IDs (which are often your email addresses). Never upload or commit your configured code to a public GitHub repository. Add your script path to .gitignore or keep configured files in a local-only directory (e.g., My_Code.gs).
- Open Google Calendar.
- On the left side, next to Other calendars, click Add other calendars (+) > Create new calendar.
- Name it something clear like
My AvailabilityorPublic Schedule. This will serve as your destination calendar.
You need the unique ID of every calendar you want to sync from (sources) and write to (destination).
- Hover over a calendar in the left-hand list, click the three vertical dots (โฎ), and select Settings and sharing.
- Scroll down to the Integrate calendar section.
- Locate and copy the Calendar ID (for primary calendars, this is your email address; for custom calendars, it ends in
@group.calendar.google.com). - Keep these IDs handy for configuration.
- Go to script.google.com (ensure you are logged into the correct Google account).
- Click New project in the top-left.
- Click on Untitled project at the top and rename it to
Calendar Sync Service.
- Delete any boilerplate code inside the Apps Script editor.
- Copy the entire contents of Code.gs (or your local
My_Code.gs) and paste it into the editor. - Customize the parameters at the top of the file under the
// --- CONFIGURATION ---section using your copied Calendar IDs (see the Configuration Reference below). - Click the Save project (floppy disk) icon or press
Ctrl + S.
- In the left-hand sidebar of the script editor, click the clock icon (Triggers).
- Click the + Add Trigger button in the bottom-right.
- Configure the trigger with the following settings:
- Function to run:
processSyncBatch - Deployment to run from:
Head - Event source:
Time-driven - Type of time-based trigger:
Minutes timer - Minute interval:
Every 5 minutes(The recommended sweet spot for responsiveness and quota safety).
- Function to run:
- Click Save.
- Upon saving your trigger, Google will display a security pop-up requesting permissions.
- Select your Google account.
- You will see a "Google hasn't verified this app" screen. Click on Advanced at the bottom, then click Go to Calendar Sync Service (unsafe) to proceed.
- Click Allow on the permissions screen.
Modify these constants at the top of your Code.gs script to define your sync environment:
| Constant | Data Type | Requirement | Description |
|---|---|---|---|
SOURCE_CALENDAR_IDS |
string[] |
Required | Array of source Calendar IDs to read events from. |
DESTINATION_CALENDAR_ID |
string |
Required | The ID of your separate public/availability calendar. |
EVENT_TITLE |
string |
Required | Default fallback title if a source calendar is not listed in CALENDAR_NAMES (e.g., "Busy"). |
CALENDAR_NAMES |
Object |
Optional | Key-value mapping of source IDs to custom titles (e.g., "Personal", "Work", "Class"). |
SYNC_START_DATE |
string |
Required | Format: YYYY-MM-DD. Earliest date to sync during manual full-history runs. |
SYNC_TAG |
string |
Required | Unique string key (default: "sync-id:auto-generated") stored in event descriptions to prevent the script from deleting your manually added destination events. |
TIMEZONE |
string |
Optional | IANA Timezone ID (e.g., "America/New_York"). Leave blank "" to dynamically inherit your calendar's primary timezone. |
SYNC_DAYS_BEFORE |
number |
Required | Number of past days to actively sync on every rolling interval (default: 7). |
SYNC_DAYS_AFTER |
number |
Required | Number of future days to actively sync on every rolling interval (default: 30). |
Once your destination calendar is populated, you can share it with booking services, employers, or clients.
- Go to your destination calendar's Settings and sharing.
- Under Access permissions for events, check the box next to Make available to public.
- In the warning pop-up, click OK.
- In the dropdown menu, select See all event details. (Since the script automatically masks your private event details, it is safe to show event details).
- Scroll down to the Integrate calendar section and copy the Public URL to this calendar to share.
For coworkers or partners who want to subscribe to your availability directly inside their own calendar client (Outlook, Apple Calendar, or another Google account):
- In the destination calendar's Integrate calendar settings section, locate the Public address in iCal format URL.
- Copy the URL and share it. They can import this link directly into their calendar app to see your availability update in real-time.
Google Apps Script runs in a shared cloud environment and is subject to Google's standard developer quotas:
- Calendar Permissions Error: If the execution log displays
Could not access calendar [ID], verify that your Google account has permission to view that calendar. If the source calendar was shared from another account, ensure it is set to at least See all event details. - Daily Executions Limit: Running the sync every 5 minutes utilizes roughly 288 executions per day, which sits comfortably within Google's standard consumer quota of 20,000 executions per day.
- Quota Limit Warnings: If you notice API rate-limit errors in the execution log, the script's built-in Exponential Backoff (
callWithBackoff) will automatically pause, sleep, and safely retry the request. No manual intervention is needed. - How to View Logs:
- Go to script.google.com and open your project.
- Click on the Executions tab in the left-hand sidebar to inspect timestamps, run states, and log entries.
If you decide to stop using the sync service and wish to cleanly purge all automatically generated availability events from your destination calendar:
- In the Google Apps Script project editor, select the Triggers tab (clock icon) and delete the time-driven trigger.
- Temporarily edit your
Code.gsscript: set bothSYNC_DAYS_BEFOREandSYNC_DAYS_AFTERparameters to0(or setSOURCE_CALENDAR_IDSto an empty array[]). - Manually execute the
processSyncBatchfunction once from the script editor toolbar. The script's reconciliation diff engine will identify all existing synced events as orphaned and cleanly delete them. - If you have legacy Script Properties you'd like to wipe out, run the
resetSynchelper function from the top toolbar dropdown. - You can now safely delete the Google Apps Script project.
This project is licensed under the Apache License 2.0. See the LICENSE file for full details.