Mirror project deadlines as calendar events
Sync the Projects sheet due-date column to Northwind's calendar — they show up alongside meetings.
Published Jul 4, 2025
Northwind’s project list lives in a spreadsheet — every active job with its client, brief, and due date in one row. That sheet is the source of truth, but it is invisible from the calendar where the team actually plans their days. Deadlines slip past because they were never alongside the meetings.
This script mirrors each project’s due date as an all-day calendar event, then remembers the event ID so future runs update the same event instead of creating duplicates. Edit the date in the sheet, the calendar follows. Add a new row, a new event appears.
What you’ll need
- A
Projectssheet with these headers in row 1:project,client,dueDate,eventId. Paste the spreadsheet ID intoPROJECTS_SHEET_ID. - The
dueDatecolumn must contain real dates (format the column as Date in the sheet) — string dates will be ignored by theinstanceof Dateguard. - Leave
eventIdblank for new rows. The script fills it in on the first run.
The script
// The spreadsheet that holds the Projects list. Headers in row 1 must be
// project, client, dueDate, eventId.
const PROJECTS_SHEET_ID = '1abcProjectsId';
/**
* Mirrors each row of the Projects sheet as an all-day calendar event on
* the user's default calendar. Stores the event ID back to the sheet so
* subsequent runs update the same event rather than creating duplicates.
*/
function syncProjectDeadlines() {
const sheet = SpreadsheetApp.openById(PROJECTS_SHEET_ID).getSheets()[0];
const values = sheet.getDataRange().getValues();
if (values.length < 2) {
Logger.log('No project rows — nothing to sync.');
return;
}
// 1. Split off the header and build a name -> column-index map. That way
// the rest of the script reads as r[col.dueDate] rather than r[2].
const [header, ...rows] = values;
const col = Object.fromEntries(header.map((k, i) => [k, i]));
const cal = CalendarApp.getDefaultCalendar();
// 2. Walk each row. Skip anything without a real Date in dueDate.
rows.forEach((r, i) => {
const due = r[col.dueDate];
if (!(due instanceof Date)) return;
const title = `Deadline: ${r[col.project]} (${r[col.client]})`;
if (r[col.eventId]) {
// 3a. Existing event — move it to the current dueDate, in case the
// date changed in the sheet since the last run.
const ev = cal.getEventById(r[col.eventId]);
if (ev) {
ev.setAllDayDate(due);
} else {
// The stored ID no longer exists (event deleted in calendar).
// Recreate and record the new ID.
const fresh = cal.createAllDayEvent(title, due);
values[i + 1][col.eventId] = fresh.getId();
}
} else {
// 3b. New row — create a fresh all-day event and remember its ID.
const ev = cal.createAllDayEvent(title, due);
values[i + 1][col.eventId] = ev.getId();
}
});
// 4. Write the whole grid back so any newly-captured eventIds persist.
sheet.getDataRange().setValues(values);
Logger.log('Synced ' + rows.length + ' project row(s).');
}
How it works
syncProjectDeadlinesreads the entireProjectssheet and bails if there are no data rows.- It builds a
colmap from the header so the rest of the script names columns instead of indexing by number. - For each row, it skips any without a real
DateindueDate— strings and blanks slip past harmlessly. - If the row already has an
eventId, it looks the event up and moves it to the current date withsetAllDayDate. If the stored ID has been deleted in the calendar, it recreates the event and updates the row. - If the row has no
eventId, it creates a new all-day event and writes the ID back into the values grid in memory. - After the loop, a single
setValuescall writes the entire grid back — so every newly-captured ID persists.
Example run
Before the run, the Projects sheet looks like this:
| project | client | dueDate | eventId |
|---|---|---|---|
| Spring campaign | Fabrikam | 2025-09-15 | |
| Brochure refresh | Contoso | 2025-09-22 | |
| Website launch | Adventure Works | 2025-10-01 | [email protected] |
After a run, three all-day events appear on the calendar — Deadline: Spring campaign (Fabrikam) on 15 September, and so on — and the sheet’s eventId
column is filled in:
| project | client | dueDate | eventId |
|---|---|---|---|
| Spring campaign | Fabrikam | 2025-09-15 | [email protected] |
| Brochure refresh | Contoso | 2025-09-22 | [email protected] |
| Website launch | Adventure Works | 2025-10-01 | [email protected] |
Change the Website launch date to 8 October in the sheet, run again, and the existing event moves to the new date — same ID, no duplicate.
Trigger it
Run this on a schedule so sheet edits make it onto the calendar without anyone remembering:
- In the Apps Script editor, open Triggers (the clock icon).
- Add a trigger for
syncProjectDeadlines, time-driven, day timer, very early (3am–4am). - Save and approve the calendar and spreadsheet authorisation prompts.
You can also wire it to onEdit for instant sync, but the daily trigger is
cheaper and less noisy.
Watch out for
- The sync is one-way, sheet to calendar. Move an event by dragging it on the calendar and the next run will yank it back to the sheet’s date. If you want calendar edits to win, store the calendar date too and compare before deciding which to keep.
- Deleting a row in the sheet does not delete the event. Add an
archivedcolumn and check for it in the loop if you want hands-off cleanup — or delete events manually when you delete the row. setAllDayDateonly updates the date; the title stays as it was when the event was first created. To rename, callev.setTitle(title)next to thesetAllDayDatecall.- The whole grid is rewritten via
setValues. If anyone else is editing the sheet at the same moment, their change will be overwritten. Run the trigger overnight to avoid this.
Related
Schedule personal habits and routines
Block recurring habits on Awadesh's calendar — gym, walks, deep-work mornings.
Updated Nov 5, 2025
Sync birthdays and anniversaries to Calendar
Populate recurring personal dates from a Sheet — the Northwind team rituals calendar.
Updated Nov 1, 2025
Generate recurring events with custom exceptions
Handle complex recurrence rules in code — every Tuesday except UK bank holidays.
Updated Oct 28, 2025
Auto-reschedule low-priority conflicts
Move flexible Northwind events around fixed ones — focus blocks bend, client calls don't.
Updated Oct 16, 2025
Build a contract-renewal calendar
Track Northwind's recurring-revenue renewal dates as calendar events for proactive sales.
Updated Oct 8, 2025