Triggers explained: simple, installable, and time-based
When to use each type of Apps Script trigger and the gotchas to avoid.
Published Jun 25, 2025
A trigger is what makes an Apps Script run without anyone pressing a button. It connects an event — a spreadsheet edit, a form submission, a scheduled time — to a function, so the script reacts on its own. Triggers are the difference between a script you run by hand and an automation that just works.
Apps Script offers three kinds of trigger, and they are not interchangeable. They differ in what fires them, whose account they run under, and what they are permitted to do. Choosing the wrong one is the source of most “why didn’t my script run” confusion. This guide explains all three and when to reach for each.
Three flavours
| Type | Fires on | Runs as | Auth needed |
|---|---|---|---|
Simple (onOpen, onEdit) | UI events | The user who triggered the event | None |
| Installable (programmatic) | UI events, time, calendar, form | The script owner | Yes — install once |
| Time-based | A schedule | The script owner | Yes |
The two questions that decide which one you need:
- What does the function need to do? If it sends email, reads another file, or calls an external service, a simple trigger cannot do it — you need an installable one.
- When does it need to run? If the answer is “on a schedule” rather than “in response to a user”, you need a time-based trigger.
Simple
Simple triggers are the easiest to use because there is nothing to set up. Name
a function exactly onOpen or onEdit and Apps Script wires it automatically.
They run instantly under the account of whoever caused the event.
That convenience comes with strict limits. A simple trigger runs in a restricted context: it cannot send email, cannot open other documents, and cannot call any service that needs authorisation. It is built for fast, self-contained tweaks to the file the user is already in.
// Runs the moment a cell is edited. Highlights any edit in column A.
function onEdit(e) {
// e.range is the edited cell; e.value, e.oldValue are also available.
if (e.range.getColumn() === 1) {
e.range.setBackground('#fff4d6');
}
}
Use a simple trigger for instant UI feedback — formatting, validation, highlighting — that stays within the current file and needs no permissions.
Installable
An installable trigger handles everything a simple trigger cannot. It runs as the script owner with full authorisation, so it may send mail, touch other files, and call external APIs. The trade-off is that it must be created deliberately, either through the editor’s Triggers panel or in code.
// Run this function once to register the trigger. After that, the
// installable trigger fires on every edit until you delete it.
function setupOnEditTrigger() {
ScriptApp.newTrigger('handleEdit')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create();
}
// The handler runs with the owner's permissions, so it can send email.
function handleEdit(e) {
GmailApp.sendEmail('[email protected]', 'Edit happened', JSON.stringify(e));
}
Note the function names: setupOnEditTrigger is run once by hand to install the
trigger; handleEdit is what actually fires afterwards. Calling the setup
function twice creates a duplicate trigger, so guard against that or delete the
old one first.
Time-based
Time-based triggers run a function on a schedule with no user involvement at all. They are the engine behind every “every morning” or “once an hour” job.
// Run handleEdit's job once every hour, around the clock.
ScriptApp.newTrigger('myJob').timeBased().everyHours(1).create();
// Run it once a day, in the 8:00–9:00 window.
ScriptApp.newTrigger('myJob').timeBased().atHour(8).everyDays(1).create();
atHour(8) schedules the run for within the 8 AM hour, not exactly 08:00:00 —
Apps Script picks a moment in that window. For recurring work, prefer interval
methods like everyHours and everyDays over trying to pin an exact minute.
Gotchas
- A simple trigger will not fire for a user who has not authorised the script. An editor who has never approved permissions silently gets nothing.
- Installable and time-based triggers run as the user who created them. If that person leaves or is on PTO, scheduled jobs keep running under their account — and break if their access is revoked.
- Time-based triggers drift. A trigger set for “every hour” or “8 AM” runs within that window, not at an exact second — never depend on precise timing.
- Running a trigger-setup function more than once creates duplicate triggers that all fire. Delete existing triggers before re-installing, or check first.
- Simple triggers cannot send email, open other files, or call authorised services. If a function needs any of that, it must be installable.