appscript.dev
Automation Intermediate Forms Calendar

Create a calendar booking from a form

Schedule Northwind appointments from form data — slot, attendee, confirmation.

Published Jul 13, 2025

Northwind’s portrait sessions are short and back-to-back, which means the booking step has to be friction-free. Calendly works, but it is another subscription and another login; a Google Form already lives next to the spreadsheets the studio runs on. The trouble is the last mile — turning a form answer into an event on the photographer’s calendar without anyone copying and pasting times across tools.

This script closes that gap. When someone submits the booking form, it reads the preferred slot, creates a 30-minute event on the default calendar, and invites the attendee by email. Confirmation, calendar hold and reminder all happen in one go, with no manual scheduling.

What you’ll need

  • A Google Form with three short-answer questions named exactly Name, Email, and Preferred slot. The slot field should ask for an ISO date-time like 2026-05-27T14:30 (a “date and time” question works best).
  • A Google account whose default calendar should hold the bookings.
  • The form linked to its Apps Script project so the trigger fires on submit.

The script

// How long each appointment lasts. 30 minutes covers a portrait session
// at Northwind; change once here rather than scattering the number.
const SLOT_MINUTES = 30;

// Title prefix on the calendar event. Keeps studio events easy to scan
// from the week view.
const EVENT_TITLE_PREFIX = 'Appointment';

// Description shown inside the calendar event itself. The attendee sees
// this in their invite email and on the event detail page.
const EVENT_DESCRIPTION = 'Booked via the Northwind appointment form.';

/**
 * Runs on every form submission. Reads the requested slot and creates a
 * calendar event with the attendee invited, so the booking is confirmed
 * without anyone touching the calendar by hand.
 *
 * @param {GoogleAppsScript.Events.FormsOnFormSubmit} e Form submit event.
 */
function onFormSubmit(e) {
  // 1. Pull the three answers we need. namedValues always returns arrays.
  const name = (e.namedValues.Name && e.namedValues.Name[0]) || '';
  const email = (e.namedValues.Email && e.namedValues.Email[0]) || '';
  const slotIso = (e.namedValues['Preferred slot'] && e.namedValues['Preferred slot'][0]) || '';

  // 2. Refuse to schedule with missing data. Skipping is cheaper than
  //    creating a junk event and chasing it down later.
  if (!name || !email || !slotIso) {
    Logger.log('Skipping booking — missing field: ' + JSON.stringify(e.namedValues));
    return;
  }

  // 3. Parse the slot. Forms hand back strings, and an unparseable date
  //    yields Invalid Date — guard against that explicitly.
  const start = new Date(slotIso);
  if (isNaN(start.getTime())) {
    Logger.log('Skipping booking — unparseable slot: ' + slotIso);
    return;
  }
  const end = new Date(start.getTime() + SLOT_MINUTES * 60_000);

  // 4. Create the event on the default calendar and invite the attendee.
  //    `guests` triggers a Google Calendar invite email automatically.
  CalendarApp.getDefaultCalendar().createEvent(
    `${EVENT_TITLE_PREFIX}: ${name}`,
    start,
    end,
    {
      guests: email,
      sendInvites: true,
      description: EVENT_DESCRIPTION,
    }
  );

  Logger.log(`Booked ${name} (${email}) at ${start.toISOString()}`);
}

How it works

  1. onFormSubmit receives the event payload from the form trigger, which includes e.namedValues keyed by question title.
  2. It reads the three answers we care about, defaulting to empty strings so a missing field does not throw.
  3. It bails out early if any required field is missing — better a quiet skip than a malformed calendar event in the photographer’s week.
  4. It parses the slot string into a Date. ISO date-times from the Forms “date and time” field parse cleanly; anything else falls into the isNaN guard.
  5. It computes the end time by adding SLOT_MINUTES worth of milliseconds.
  6. It calls CalendarApp.getDefaultCalendar().createEvent with the attendee as a guest. The sendInvites: true option is what triggers the Google-generated confirmation email, so no separate GmailApp call is needed.

Example run

A potential client submits the form with:

FieldValue
NameMaya Okafor
Email[email protected]
Preferred slot2026-05-29T11:00

Immediately after submission, the photographer’s default Google Calendar gains an event titled Appointment: Maya Okafor from 11:00 to 11:30 on 29 May 2026, described as “Booked via the Northwind appointment form.” Maya receives a Google Calendar invite at [email protected] with the same details and an Accept/Maybe/Decline prompt.

The Apps Script log records Booked Maya Okafor ([email protected]) at 2026-05-29T11:00:00.000Z.

Trigger it

The trigger is wired to the form, not the clock:

  1. From the bound Apps Script project, open Triggers.
  2. Add a trigger for onFormSubmit, source From form, type On form submit.
  3. Approve the Calendar and Forms scopes when prompted on the first run.

Test by submitting the form yourself with your own email — the invite arrives in your inbox within a few seconds.

Watch out for

  • The script does not check for clashes. Two people booking the same slot will both get events at the same time. To prevent that, query the calendar for events in the requested window before creating the new one and bail out if any are returned.
  • Time zones bite. A naive ISO string like 2026-05-29T11:00 is interpreted in the script’s time zone — set the project’s time zone to the studio’s zone under Project settings to avoid surprises.
  • The “date and time” form field is the most reliable input. Free-text dates ("next Tuesday at 11") will not parse. Either constrain the question type or accept the occasional isNaN skip.
  • Calendar invites send automatically via sendInvites: true. If the studio prefers a branded confirmation, drop that to false and send a styled email — see Send branded confirmation emails on submission.

Related