This Google Apps Script creates birthday events in your Google Calendar based on your Google Contacts.
This project uses Google Apps Script and the Google People API.
- Open Google Apps Script and create a new project.
- Paste the contents of
code.gsinto the editor. - In the left sidebar, go to Services and add the People API.
- Modify the
CONFIGsection at the top of the script (see below). - Run
loopThroughContacts()once to:- Generate birthday events
- Optionally clean up outdated ones
- Set up a time-based trigger (if enabled)
⚠️ DATA-LOSS WARNING
The script deletes any all-day event whose date and title look like a birthday so it can rebuild a clean set each run.
That can accidentally match anniversaries, retirements, “Remembrance”, etc.
Protect yourself:
- Use a separate calendar (create one called “Birthdays”) and point
CONFIG.calendarIdto it.- Keep
CONFIG.cleanupEvents = falseuntil you have verified everything on a test calendar.- If something disappears, restore it from Calendar Trash/Bin within 30 days.
The CONFIG object gives you full control over how events are created and managed:
const CONFIG = {
calendarId: 'primary', // 'primary' or your custom calendar ID
// Title customization
useEmoji: true, // Add 🎂 emoji to event titles
showYearOrAge: true, // Recurrence on: shows (*YYYY), off: shows (age)
showAgeOnRecurring: false, // If true, shows (age) on recurring events instead of (*YYYY)
// Language and localization
language: 'en', // Language code: 'en' (English), 'it' (Italian), etc.
titleFormat: '{emoji}{name} ({ageOrYear})', // Title format template (see below for placeholders)
// Recurrence
useRecurrence: true, // Create recurring yearly events
futureYears: 20, // Recurring events end this many years in the future
pastYears: 2, // Recurring events start this many years in the past
// Reminder settings
useReminders: true, // Enable/disable popup reminders for birthday events
reminderMinutesBefore: 1440, // Popup reminder time (in minutes) - only used if useReminders is true
// Common values: 0 = at event time, 60 = 1 hour before, 1440 = 1 day before, 10080 = 1 week before
// Availability settings
setTransparency: false, // Events display you as busy by default (Transparency OPAQUE). Set to true to display you as available (Transparency TRANSPARENT).
// Cleanup
cleanupEvents: false, // ⚠️⚠️⚠️ Deletes all matching birthday events between ±100 years
// Trigger options
useTrigger: true, // Automatically run on a schedule
triggerFrequency: 'daily', // 'daily' or 'hourly'
triggerHour: 4, // If 'daily', the hour of day to run (0–23)
// Script identification
scriptKey: 'CREATED_BY_Auto-Birthdays', // Unique identifier for events created by this script
// Contact label filtering (optional)
useLabels: false, // Enable filtering contacts by labels
contactLabels: [], // Array of contact label IDs to include (e.g. ['abc123'])
// Month filtering (optional)
useMonthFilter: false, // Enable filtering contacts by birth month
filterMonths: [] // Array of months to include (1-12), e.g. [1, 2, 4] for Jan, Feb, Apr
};You can optionally filter which contacts are processed based on their labels (contact groups). This is useful if you only want birthday events for specific groups of contacts.
-
Find your label IDs:
- Open https://contacts.google.com/
- Click on your desired label/group
- Note the page address:
https://contacts.google.com/label/[contactLabelID] - The last part of the address is your
contactLabelID
-
Configure label filtering:
const CONFIG = { // ... other settings useLabels: true, // Enable label filtering contactLabels: ['abc123'] // Only process contacts with this label };
-
Run the script - only contacts with the specified labels will have birthday events created.
| Setting | Description | Default | Example |
|---|---|---|---|
useLabels |
Enable/disable label filtering | false |
true |
contactLabels |
Array of contact label IDs to include | [] |
['abc123', 'def456'] |
To improve performance and reduce the risk of timeouts, this filter can be used to process contacts in smaller groups. You can optionally filter which contacts are processed based on the month of their birthday.
⏱️ EXECUTION TIME LIMIT
Google Apps Script has a 6-minute maximum execution time. If you have many contacts, the script may timeout before completing. Use month filtering to process contacts in smaller batches, or simply re-run the script to continue where it left off.
-
Configure month filtering:
const CONFIG = { // ... other settings useMonthFilter: true, // Enable month filtering filterMonths: [1, 2, 3] // Only process contacts with birthdays in Jan, Feb, Mar };
-
Run the script - only contacts with birthdays in the specified months will have birthday events created.
| Setting | Description | Default | Example |
|---|---|---|---|
useMonthFilter |
Enable/disable month filtering | false |
true |
filterMonths |
Array of months to include (1-12) | [] |
[1, 2, 4] for January, February, April |
The script supports multiple languages for event titles and descriptions. You can customize both the language and the title format to suit your preferences.
Currently supported languages:
- English (
'en') - Default - Italian (
'it') - French (
'fr') - German (
'de') - Spanish (
'es')
You can customize the event title format using these placeholders:
| Placeholder | Description | Example |
|---|---|---|
{emoji} |
Birthday emoji (🎂 ) if enabled | 🎂 |
{name} |
Person's name | John Doe |
{age} |
Person's age | 36 |
{ageOrYear} |
Age or birth year (*YYYY) based on settings | 36 or *1988 |
{ageText} |
Age with language-specific year/years word | 36 years |
{birthYear} |
Birth year | 1988 |
{birthday} |
Localized word for "birthday" | birthday / compleanno |
{years} |
Localized word for "years" | years / anni |
{year} |
Localized word for "year" (singular) | year / anno |
English Configuration:
language: 'en',
titleFormat: '{emoji}{name} ({ageOrYear})' // 🎂 John Doe (36)
// or
titleFormat: '{emoji}{name}\'s birthday - {age} years' // 🎂 John Doe's birthday - 36 yearsItalian Configuration:
language: 'it',
titleFormat: '{emoji}Compleanno di {name} - {age} anni' // 🎂 Compleanno di John Doe - 36 anni
// or
titleFormat: '{emoji}{name} - {ageText}' // 🎂 John Doe - 36 anniFrench Configuration:
language: 'fr',
titleFormat: '{emoji}Anniversaire de {name} - {age} ans' // 🎂 Anniversaire de John Doe - 36 ans
// or
titleFormat: '{emoji}{name} - {ageText}' // 🎂 John Doe - 36 ansGerman Configuration:
language: 'de',
titleFormat: '{emoji}Geburtstag von {name} - {age} Jahre' // 🎂 Geburtstag von John Doe - 36 Jahre
// or
titleFormat: '{emoji}{name} - {ageText}' // 🎂 John Doe - 36 JahreSpanish Configuration:
language: 'es',
titleFormat: '{emoji}Cumpleaños de {name} - {age} años' // 🎂 Cumpleaños de John Doe - 36 años
// or
titleFormat: '{emoji}{name} - {ageText}' // 🎂 John Doe - 36 añosYou have two ways to customize titles:
- Use built-in language formats - Set
languageto use the default format for that language - Custom format - Set your own template in
titleFormatwith any placeholders you want
Depending on your configuration, birthday events will appear with different formats. Here are examples for different languages:
useEmoji |
useRecurrence |
showYearOrAge |
showAgeOnRecurring |
Event Title Example |
|---|---|---|---|---|
| true | true | true | false | 🎂 John Doe (*1988) |
| true | true | true | true | 🎂 John Doe (36) |
| true | false | true | N/A | 🎂 John Doe (36) |
| false | true | false | N/A | John Doe |
| false | false | true | N/A | John Doe (36) |
| false | false | false | N/A | John Doe |
// English birthday style
titleFormat: '{emoji}{name}\'s birthday - {age} years'
// Result: 🎂 John Doe's birthday - 36 years
// Simple format
titleFormat: '{emoji}{name} - {age}'
// Result: 🎂 John Doe - 36
// Italian with age text
titleFormat: '{emoji}{name} compie {ageText}'
// Result: 🎂 John Doe compie 36 anniNotes:
showAgeOnRecurringonly applies whenuseRecurrence=trueandshowYearOrAge=true- If no birth year is provided, age or year is omitted
- When
showAgeOnRecurring=true, individual events are created for each year instead of recurring events:- 2025: 🎂 John Doe (30)
- 2026: 🎂 John Doe (31)
- 2027: 🎂 John Doe (32)
- This allows you to see all future birthdays with correct ages when browsing your calendar
- Individual events span from
pastYearstofutureYearsrelative to the current year
Review the Configuration section to understand all available settings.
⏱️ EXECUTION TIME LIMIT
Google Apps Script has a 6-minute maximum execution time. The script may timeout before completing if you have:
- A large number of contacts (hundreds or thousands)
- Large year spans (
pastYears+futureYears> 40)- Both
useRecurrence=falseandshowAgeOnRecurring=true(creates individual events for each year)To reduce timeout risk:
- Start with smaller year spans (e.g.,
pastYears: 1,futureYears: 10)- Use month filtering to process contacts in batches
- If the script times out, simply re-run it - it will continue where it left off
- Consider using
useRecurrence=truefor better performance with large datasets
If CONFIG.cleanupEvents is enabled:
- Search your calendar between 100 years in the past and future
- Find outdated or duplicate birthday events created by this script only
- Delete them safely, including recurring series
- Manual birthday events are never touched
Safety Note: The cleanup only affects events containing the script's unique identifier, ensuring your manually created events remain safe.
If CONFIG.useTrigger is enabled:
- Automatically create a time-based trigger
- Run either:
- Hourly every hour
- Or Daily at
triggerHour
🛠️ If you change the trigger settings, the script will:
- Remove any existing trigger
- Install a new one with the updated config
This ensures only one correct trigger is active.
The script uses a unique key system to identify events it has created:
scriptKey: A unique identifier embedded in each event's description- Safe operation: Only modifies events it has created, leaving manual birthday events untouched
- Easy identification: You can search for events created by the script using the key
- Version tracking: Update the key for different script versions if needed
How it works:
- Each created event includes
[CREATED_BY_Auto-Birthdays]in its description - The script only deletes/updates events containing this identifier
- Manual birthday events are completely safe from script operations
You'll see all-day birthday events appear in your calendar like these:
- 🎂 John Doe (*1988)
- 🎂 Jane Smith (36)
- John Appleseed (no year provided)
All events appear as all-day events on the person’s birthday.
If you encounter issues:
- Check the Apps Script execution log
- Run the script manually the first time to authorize permissions
- Ensure you've enabled the People API under Services
- Make sure you've granted Calendar and Contacts permissions
- Script timeout: If execution stops due to the 6-minute limit, simply re-run the script to continue processing
- Restore missing items via Calendar → Trash/Bin.
Contributions are welcome! Fork this repo, improve it, and submit a pull request.
This script is released under the MIT License.
For support or feedback, please file an issue.
