Guides › Apps Script
Free Apps Script: Google Sheets to Google Calendar, without duplicates
Most tutorial scripts create every event again each time you run them. This one remembers each row's event and updates it instead. Copy it, paste it, done.
1. Lay out your sheet
| A | B | C | D | E | F |
|---|---|---|---|---|---|
| Title | Start | End | Description | Location | Event ID |
| Team meeting | 12/7/2026 9:00:00 | 12/7/2026 10:00:00 | Weekly sync | Room 3 |
Start and End must be real dates with times (they line up on the right of the cell). Leave column F empty: the script fills it.
2. Paste the script
In the sheet, open Extensions → Apps Script, delete what's there, and paste:
// Calendrow's free Sheets → Calendar script (no duplicates).
// Columns: A Title | B Start (date time) | C End (date time, optional)
// D Description | E Location | F Event ID (leave empty; the script fills it)
const CALENDAR_ID = 'primary'; // or a calendar ID from Calendar settings
function syncToCalendar() {
const lock = LockService.getDocumentLock();
if (!lock.tryLock(30000)) return; // another run is still going
try {
const sheet = SpreadsheetApp.getActiveSheet();
const cal = CALENDAR_ID === 'primary'
? CalendarApp.getDefaultCalendar()
: CalendarApp.getCalendarById(CALENDAR_ID);
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
const [title, start, end, description, location, eventId] = rows[i];
if (!title || !(start instanceof Date)) continue;
const finish = end instanceof Date && end > start
? end
: new Date(start.getTime() + 60 * 60 * 1000); // default 1 hour
let event = eventId ? cal.getEventById(eventId) : null;
if (event) {
event.setTitle(title);
event.setTime(start, finish);
event.setDescription(description || '');
event.setLocation(location || '');
} else {
event = cal.createEvent(title, start, finish, {
description: description || '',
location: location || '',
});
// Save the ID right away, so the next run updates instead of duplicating.
sheet.getRange(i + 1, 6).setValue(event.getId());
}
}
} finally {
lock.releaseLock();
}
}
function onOpen() {
SpreadsheetApp.getUi().createMenu('Calendar sync')
.addItem('Sync rows to Calendar', 'syncToCalendar')
.addToUi();
}
Save, reload the sheet, and use the new Calendar sync → Sync rows to Calendar menu. The first run asks for permission to manage your calendars.
3. How it avoids duplicates
- Event ID column: after creating an event, the script writes its ID into column F. Next time, it finds that event and updates it.
- Saves the ID immediately, row by row, so a run that stops halfway doesn't create the same events again.
- A lock stops two runs at once (a double click, or a trigger firing while you run it by hand) from both creating the same event.
What this script doesn't do
- Copied rows: if you copy a row including its Event ID, both rows point at one event. Clear F on copied rows.
- Deleted events: if an event was deleted in Calendar, clear its ID in F to recreate it.
- Text dates are skipped silently. Use Format → Number → Date time.
- Time-only End cells (like 06:00 for an overnight shift) aren't handled; use a full date and time.
- Long sheets: Apps Script stops after 6 minutes per run, and Google limits how many events an account can create per day.
- No preview and no per-row status, so you find problems by looking at the calendar.
Wrong times? See why events from Sheets show the wrong time.
Or skip the script. Calendrow does the same job from a sidebar, plus a preview before writing, a status and reason on every row, overnight shifts, all-day events and daylight-saving-safe times. Free for up to 100 new events a month.
Launching on the Google Workspace Marketplace in November 2026.