appscript.dev
Automation Intermediate Calendar Forms

Create events from appointment-form submissions

Book slots from a Google Form into the Northwind appointments calendar automatically.

Published Aug 13, 2025

Northwind takes client briefings by appointment. For a while the studio used a shared Google Form for booking, then someone copied each submission into the calendar by hand. Two weeks in, a slot was double-booked because the manual step lagged behind the form by half a day. The same copy-and-paste job is what Apps Script triggers are built for.

This script runs every time the booking form is submitted. It reads the name, email, and chosen slot, creates a 30-minute event on the studio calendar, and invites the client as a guest — all before the submitter has closed the form tab. The form is the source of truth; the calendar mirrors it automatically.

What you’ll need

  • A Google Form with three required fields named exactly name, email, and slot. The slot field should be a Date-and-time question.
  • Edit access to the calendar that should receive the appointments. The script uses your default calendar — change APPOINTMENTS_CALENDAR_ID if you keep appointments on a dedicated calendar.
  • The form linked to a Google Sheet (responses live in a sheet), so the installable trigger has somewhere to fire from.

The script

// The calendar that should receive appointments. Leave as null to use
// the script owner's default calendar; set to a calendar ID to write
// to a shared "Northwind appointments" calendar instead.
const APPOINTMENTS_CALENDAR_ID = null;

// How long each appointment lasts, in minutes. The form only captures
// the start time, so the end time is computed from this constant.
const APPOINTMENT_MINUTES = 30;

// Form field names — must match the question titles in your Google Form
// exactly, case sensitive.
const FIELD_NAME = 'name';
const FIELD_EMAIL = 'email';
const FIELD_SLOT = 'slot';

/**
 * Installable form-submit handler. Reads the submission, validates the
 * key fields, and creates a guest-invited event on the appointments
 * calendar.
 *
 * @param {GoogleAppsScript.Events.SheetsOnFormSubmit} e Form submit event.
 */
function onFormSubmit(e) {
  // 1. `namedValues` keys responses by question title. Each value is an
  //    array because Forms permits multi-answer questions; we take the
  //    first entry of each.
  const v = e.namedValues || {};
  const name = (v[FIELD_NAME] || [''])[0].trim();
  const email = (v[FIELD_EMAIL] || [''])[0].trim();
  const slotRaw = (v[FIELD_SLOT] || [''])[0].trim();

  // 2. Guard the inputs. A blank slot or a malformed date used to silently
  //    produce a "1970" event — better to log and bail.
  if (!name || !email || !slotRaw) {
    Logger.log('Skipped submission with missing fields: ' + JSON.stringify(v));
    return;
  }

  const slot = new Date(slotRaw);
  if (isNaN(slot.getTime())) {
    Logger.log('Skipped submission with unparseable slot: ' + slotRaw);
    return;
  }

  // 3. Compute the end time from the constant duration so a future
  //    redesign (45-minute calls?) is a one-line change.
  const end = new Date(slot.getTime() + APPOINTMENT_MINUTES * 60_000);

  // 4. Pick the right calendar. The default is fine for a solo studio;
  //    a calendar ID is better when several team members share the load.
  const cal = APPOINTMENTS_CALENDAR_ID
    ? CalendarApp.getCalendarById(APPOINTMENTS_CALENDAR_ID)
    : CalendarApp.getDefaultCalendar();

  // 5. Create the event with the client on the guest list. Google sends
  //    them the invite automatically — no separate email step required.
  cal.createEvent('Appointment: ' + name, slot, end, {
    guests: email,
    description: 'Booked via Northwind form on '
      + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'd MMM yyyy HH:mm'),
    sendInvites: true,
  });
}

How it works

  1. The trigger fires with an event object whose namedValues map keys responses by question title. The script pulls name, email, and slot out of it and trims each.
  2. If any required field is missing, or the slot string cannot be parsed into a real Date, the script logs the offending submission and returns. That keeps malformed bookings from creating 1970-era ghost events.
  3. The end time is computed by adding APPOINTMENT_MINUTES to the start, so the duration lives in one place at the top of the file.
  4. The calendar is chosen by the APPOINTMENTS_CALENDAR_ID constant — null means “use the script owner’s default calendar”, a string means “look up that calendar by ID and write there instead”.
  5. createEvent takes the client’s email under guests, and Google Calendar sends the invitation automatically because sendInvites is true.

Example run

A client submits the form with these answers:

FieldValue
namePriya Patel
email[email protected]
slot2026-06-04 14:00

A few seconds later the calendar contains:

TitleStartEndGuests
Appointment: Priya Patel4 Jun 2026 14:004 Jun 2026 14:30[email protected]

Priya receives the invitation in her inbox; the studio sees the appointment on the calendar without a manual copy step.

Trigger it

Form submissions cannot fire simple triggers when the script owner is offline, so the wiring needs to be an installable trigger:

  1. Open the form’s linked response spreadsheet, then Extensions → Apps Script.
  2. Paste the script above into the editor.
  3. Open Triggers, click Add trigger, and pick onFormSubmit, event source From spreadsheet, event type On form submit.
  4. Approve the authorisation prompt the first time — the script needs permission to read the form responses and write to Calendar.

Watch out for

  • The field names in the form (name, email, slot) must match FIELD_NAME, FIELD_EMAIL, and FIELD_SLOT exactly, including case. Rename the question in the form and the script silently sees an empty value on the next submission.
  • The slot string comes from a Date-and-time question and is parsed in the script’s time zone. If your form takes bookings from clients across time zones, capture the zone as a separate field rather than guessing.
  • The script does not check for overlap with existing events. Two submissions for the same slot will create two appointments. Add an availability check with cal.getEvents(slot, end) if double-booking is a real risk.
  • An installable trigger fails silently when the script owner’s authorisation expires (for instance after a password change). Open the project’s Executions tab once a month and confirm form submits are still landing.

Related