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)
// Event appearance
eventColor: '4', // Event color (see color chart below)
// Language and localization
language: 'en', // Language code: 'en' (English), 'it' (Italian), etc.
titleFormat: '', // Custom title format (empty = use language default)
// Recurrence
useRecurrence: true, // Create recurring yearly events
futureYears: 10, // Recurring events end this many years in the future
pastYears: 1, // 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)
// Common values: 0 = at event, 60 = 1hr, 1440 = 1 day, 10080 = 1 week
// Cleanup
firstRunCleanup: false, // β οΈ ONE-TIME: Set true to remove old "xxx's Birthday" events
manualCleanup: false, // β οΈ ONE-TIME: Set true to delete ALL script-created events
monthlyCleanup: true, // Run full cleanup on the 1st of each month (deletes & recreates)
cleanupOrphans: true, // Delete birthday events for contacts that no longer exist
// 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: '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
// Month filtering (optional)
useMonthFilter: false, // Enable filtering contacts by birth month
filterMonths: [] // Array of months to include (1-12)
};Customize the color of birthday events in your calendar. Google Calendar supports the following event colors:
| Color Code | Color Name | Description | Hex Color |
|---|---|---|---|
'1' |
Lavender | Pale purple | #a4bdfc |
'2' |
Sage | Light green | #7ae7bf |
'3' |
Grape | Purple | #dbadff |
'4' |
Flamingo | Pink | #ff887c |
'5' |
Banana | Yellow | #fbd75b |
'6' |
Tangerine | Orange | #ffb878 |
'7' |
Peacock | Teal/Cyan | #46d6db |
'8' |
Graphite | Gray | #e1e1e1 |
'9' |
Blueberry | Blue | #5484ed |
'10' |
Basil | Green | #51b749 |
'11' |
Tomato | Red | #dc2127 |
'' or null |
Default | Calendar's default color | - |
Popular choices for birthdays:
eventColor: '4', // π€© Flamingo (pink) - festive and cheerful
eventColor: '5', // π Banana (yellow) - bright and celebratory
eventColor: '11', // π Tomato (red) - bold and noticeable
eventColor: '9', // π Blueberry (blue) - calm and classic
eventColor: '', // Use your calendar's default colorNote: To apply color changes to existing events, set manualCleanup: true, run the script once to delete old events, then set it back to false and run again to recreate them with the new color.
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: '' // π John Doe's Birthday (36) - default
// or
titleFormat: '{emoji}{name} ({ageOrYear})' // π John Doe (36)
// or
titleFormat: '{emoji}{name}\'s birthday - {age} years' // π John Doe's birthday - 36 yearsNote: If the contact doesn't have a birth year specified, age/year is automatically hidden:
- With year: π John Doe's Birthday (36)
- Without year: π John Doe's Birthday
Italian 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
The script provides three cleanup mechanisms:
A one-time cleanup to remove legacy birthday events created before using this script:
- Set
firstRunCleanup: true - Run the script once
- Set
firstRunCleanup: false
Patterns removed:
- "John's Birthday", "John's Bday"
- "Birthday of John", "Birthday - John"
- Localized versions (Italian, French, German, Spanish)
Note: Only removes events not created by this script.
Automatically runs on the 1st of each month:
- Deletes all script-created birthday events
- Immediately recreates them with current contact data
- Handles birthday date changes and deleted birthdays
- Runs during normal scheduled execution
Runs on every execution:
- Finds birthday events for contacts that no longer exist
- Deletes only script-created orphaned events
- Safe for your manual events
| Setting | When it runs | What it does |
|---|---|---|
firstRunCleanup |
Once (manual) | Removes legacy "xxx's Birthday" events |
monthlyCleanup |
1st of month | Full delete & recreate of all events |
cleanupOrphans |
Every run | Removes events for deleted contacts |
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's Birthday (*1988)
- π Jane Smith's Birthday (36)
- π John Appleseed's Birthday (no year provided, so no age shown)
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.
