appscript.dev
Guide Intermediate Sheets Forms Calendar

Use installable triggers for event-driven design

Build reactive Northwind automations — fire on edits, form submits, calendar events.

Published Sep 25, 2025

Most useful automations are reactive: something happens — a cell changes, a form is submitted, a calendar event approaches — and code should run in response. Apps Script supports this through triggers, which connect an event to a function. But there are two kinds of trigger, and the difference between them decides whether your automation can actually do its job.

Simple triggers are the easy default and the wrong choice for almost any real workflow, because they run with no authorisation. Installable triggers run with the full permissions of the person who set them up, which is what reactive automation usually needs. This guide explains the distinction and how to build on the installable kind.

Why installable

Simple triggers — functions literally named onEdit or onOpen — run automatically with no setup, but they run in a sandbox. They execute as the user who caused the event, with no authorisation, so they cannot send email, fetch a URL, or open a different file. They are limited on purpose, to keep an unsuspecting viewer from running powerful code.

Installable triggers are different. You create them deliberately, and they run as the account that installed them, carrying that account’s full permissions. The trade-offs:

Simple triggerInstallable trigger
SetupAutomatic by function nameCreated in code or UI
Runs asThe user causing the eventThe installer
AuthorisationNone — sandboxedFull installer permissions
Can send email / fetch URLsNoYes
Can open other filesNoYes
Event typesonEdit, onOpen, onFormSubmit (limited)Edit, form submit, time-based, calendar

If your handler needs to do anything beyond reading and writing the current document, you need an installable trigger.

Set them up

Installable triggers are created with the ScriptApp trigger builder. A common pattern is one setup function that installs everything, so the configuration lives in code rather than in a settings panel:

function installAll() {
  // The spreadsheet these triggers will watch.
  const ss = SpreadsheetApp.openById('1abcId');

  // Fire onEditHandler whenever any cell in the spreadsheet changes.
  ScriptApp.newTrigger('onEditHandler')
    .forSpreadsheet(ss)
    .onEdit()
    .create();

  // Fire onFormSubmitHandler whenever a linked form is submitted.
  ScriptApp.newTrigger('onFormSubmitHandler')
    .forSpreadsheet(ss)
    .onFormSubmit()
    .create();
}

Run installAll once from the editor. It authorises the script and registers the triggers under your account. Because the trigger names a handler function (onEditHandler), not onEdit, it stays installable — and that handler can send mail, call other services, and touch other files.

Event objects

Every trigger handler receives an event object describing what happened. Reading it lets the handler react precisely instead of reprocessing everything:

function onEditHandler(e) {
  // e.range    — the Range that was edited
  // e.value    — the new cell value
  // e.oldValue — the previous value (single-cell edits only)
  // e.user     — the user who made the edit

  // Only act when column B of the active sheet changed.
  if (e.range.getColumn() !== 2) return;

  console.log(`${e.user} changed B${e.range.getRow()} to ${e.value}`);
}

The fields differ by event type — a form submission gives you e.namedValues and e.values, a calendar trigger gives you e.calendarId. Always guard against the case where a field is missing: e.oldValue, for example, is undefined when several cells are edited at once.

Watch out for

  • Installable triggers are tied to one person. A trigger installed by you runs as you and spends your quota. If that account is suspended — someone leaves the organisation — the trigger stops firing silently. Install critical triggers under a shared or service account.
  • Duplicate installs stack up. Running installAll twice creates two of every trigger, so handlers run twice per event. Delete existing triggers before reinstalling, or check ScriptApp.getProjectTriggers() first.
  • Handlers must be fast and resilient. Triggers run unattended with no one watching for errors. Keep handlers under the execution limit and wrap risky work so one bad event does not stop the rest.
  • Edit events can fire in bursts. A paste or fill-down produces many edits quickly; design the handler to be cheap and idempotent rather than assuming one tidy edit at a time.
  • Triggers need auditing over time. It is easy to lose track of what is installed where. Use Manage triggers programmatically to list, audit and reinstall triggers across projects.