appscript.dev
Automation Intermediate Docs Calendar Drive

Auto-create meeting-notes Docs from calendar events

Pre-fill a notes template for every meeting in the next 24 hours and attach it to the event.

Published Jun 29, 2025

Good meetings get written down; the trouble is the writing-down never starts until someone scrambles for a blank Doc as the call begins. By then the agenda is half-forgotten and the notes start late. Northwind wanted a notes Doc ready and waiting before every meeting, with the basics already filled in.

This script looks ahead at the next 24 hours of calendar events, copies a notes template for each one, fills in the title, date and attendee list, and links the finished Doc straight into the event description. By the time anyone joins the call, the notes Doc is one click away with the header already done.

What you’ll need

  • A notes template Google Doc containing the placeholders {{title}}, {{date}} and {{attendees}} wherever you want those values to appear.
  • A Notes/ Drive folder for the generated Docs to land in.
  • Both IDs to hand — the template Doc ID and the Notes folder ID.

The script

// The notes template Doc, with {{title}}, {{date}} and {{attendees}}.
const TEMPLATE = '1abcNotesTemplateId';

// The Drive folder where generated notes Docs are stored.
const NOTES_FOLDER = '1abcNotesFolderId';

// How far ahead to look for meetings, in milliseconds (24 hours).
const LOOKAHEAD_MS = 24 * 60 * 60 * 1000;

/**
 * Finds every meeting in the next 24 hours, creates a pre-filled notes
 * Doc for each one from the template, and links it into the event.
 */
function prepNotesForUpcoming() {
  // 1. Build the time window: now until 24 hours from now.
  const start = new Date();
  const end = new Date(start.getTime() + LOOKAHEAD_MS);
  const events = CalendarApp.getDefaultCalendar().getEvents(start, end);

  if (!events.length) {
    Logger.log('No meetings in the next 24 hours — nothing to do.');
    return;
  }

  for (const event of events) {
    // 2. Skip events that already have a notes Doc. The 'notes-doc'
    //    tag is the marker that stops duplicates on the next run.
    if (event.getTag('notes-doc')) continue;

    // 3. Copy the template into the Notes folder, named by date + title.
    const name = `${Utilities.formatDate(event.getStartTime(), 'GMT', 'yyyy-MM-dd')} — ${event.getTitle()}`;
    const file = DriveApp.getFileById(TEMPLATE)
      .makeCopy(name, DriveApp.getFolderById(NOTES_FOLDER));

    // 4. Open the copy and swap each placeholder for real values.
    const doc = DocumentApp.openById(file.getId());
    const body = doc.getBody();
    body.replaceText('{{title}}', event.getTitle());
    body.replaceText('{{date}}',
      Utilities.formatDate(event.getStartTime(), 'GMT', 'd MMM yyyy HH:mm'));
    body.replaceText('{{attendees}}',
      event.getGuestList().map((g) => g.getEmail()).join(', '));
    doc.saveAndClose();

    // 5. Append a link to the Doc in the event description.
    event.setDescription(`${event.getDescription() || ''}\n\nNotes: ${file.getUrl()}`);

    // 6. Tag the event so the next run knows to skip it.
    event.setTag('notes-doc', file.getId());
  }
}

How it works

  1. prepNotesForUpcoming builds a time window from now to 24 hours ahead and pulls every event in that range from the default calendar. If there are none, it logs a message and stops.
  2. For each event it checks the notes-doc tag. If the tag is present, a Doc was already made on an earlier run, so the event is skipped — this is what keeps an hourly trigger from generating a new Doc every hour.
  3. It copies the template Doc into the Notes folder, naming the copy with an ISO date prefix and the meeting title so the folder sorts chronologically.
  4. It opens the copy and uses replaceText to swap the three placeholders for the event’s title, formatted start time, and a comma-separated list of guest email addresses, then saves and closes.
  5. It appends a Notes: <url> line to the event description, preserving any description that was already there.
  6. Finally it writes the new Doc’s ID into the event’s notes-doc tag, marking the event as done so it is never processed twice.

Example run

Say tomorrow’s calendar has one event: “Q3 campaign kickoff” at 10:00, with two guests. After a run, the Notes folder contains a new Doc:

2025-06-30 — Q3 campaign kickoff

Title: Q3 campaign kickoff Date: 30 Jun 2025 10:00 Attendees: [email protected], [email protected]

And the event description now ends with:

Notes: https://docs.google.com/document/d/…/edit

Re-run the script and nothing changes — the event already carries a notes-doc tag, so it is skipped.

Trigger it

Run this on an hourly time-based trigger so new invites are picked up promptly:

  1. In the Apps Script editor open Triggers (the clock icon).
  2. Add a trigger for prepNotesForUpcoming, Time-driven, Hour timer, every hour.

An hourly cadence means any meeting booked today has its notes Doc ready well before it starts, while the notes-doc tag guarantees each event is only ever prepared once.

Watch out for

  • Event tags are personal to your calendar copy. If someone else runs the same script against the same shared event, they will not see your notes-doc tag and may create a second Doc. Run this from one account only.
  • Dates are formatted in GMT. Through British Summer Time that is an hour behind London — swap 'GMT' for 'Europe/London' in both formatDate calls if the displayed times matter.
  • replaceText only touches the document body. Placeholders in the header, footer or a table caption are left untouched — keep {{title}}, {{date}} and {{attendees}} in the main body of the template.
  • Recurring meetings produce one Doc per instance, which is usually what you want, but a daily stand-up will steadily fill the Notes folder. Prune it periodically or exclude recurring events by title.
  • getEvents returns declined and tentative invites too. Add a getMyStatus() check if you only want notes for meetings you have accepted.

Related