Skip to content
// Apps Script

Simple vs installable triggers in Apps Script.

Simple triggers run as the active user with no OAuth prompt and a 30-second time limit. Installable triggers run as the installing user with full authorization. Knowing whose quota burns and whose permissions apply changes how you design every automated workflow.

I want to understand why my Apps Script trigger works fine when I run it manually but fails silently on a schedule or form submit.

The script

copy · paste · trigger
triggers.gs
Apps Script
// Simple trigger — no auth, 30-second budget, runs as active user
function onEdit(e) {
  var sheet = e.source.getActiveSheet();
  var cell = e.range;
  if (sheet.getName() === 'Orders' && cell.getColumn() === 3) {
    cell.offset(0, 1).setValue('Reviewed');
  }
}

// Installable trigger — full auth, 6-minute budget, runs as installing user
// Register this via: Triggers > Add Trigger > onFormSubmitInstallable
function onFormSubmitInstallable(e) {
  var responses = e.values;
  var email = responses[2];
  GmailApp.sendEmail(email, 'We got your request', 'Thanks, will follow up.');
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What the active-user constraint actually means

Simple triggers (onOpen, onEdit, onSelectionChange, onInstall, doGet, doPost) execute immediately in response to a user action, under that user's identity, with zero OAuth prompts. That sounds convenient until you try to call GmailApp, send an email, or hit an external API — those services require authorization, and simple triggers cannot request it. The script silently fails or throws a vague 'authorization required' error that shows up nowhere obvious.

The 30-second execution limit is a second wall. A simple onEdit that writes back one cell is fine. One that iterates 500 rows, calls an external API, or triggers a chain of SpreadsheetApp reads will hit the wall. The function terminates mid-run with no retry and no log entry in the execution transcript unless you have Stackdriver logging wired up.

The first time I hit this, I wasted an afternoon thinking the logic was wrong. The logic was fine; the trigger type was wrong.

Whose quota and whose permissions — the installable difference

Installable triggers are registered through the Apps Script UI (Triggers menu) or programmatically via ScriptApp.newTrigger(). When you register one, Apps Script records the identity of the installing user. Every future execution runs under that identity, regardless of who opened the spreadsheet or submitted the form.

This has two real consequences. First, the trigger can call any service the installer authorized: GmailApp, CalendarApp, UrlFetchApp, DriveApp. The installing user's OAuth scopes are in play. Second, the installing user's daily quota is what burns. Google's per-user quota for email sends is 100 per day on consumer accounts and 1,500 on Workspace. If ten teammates submit a form and the installable trigger sends a confirmation email each time, all ten emails count against the installer's quota, not the submitters'.

This matters in shared workflows. If the person who installed the trigger leaves the organization and their account is suspended, every time-based and event-based installable trigger they registered goes dark. The spreadsheet keeps working; the automations silently stop.

Choosing the right type for the job

Use a simple trigger when: the operation is fast (well under 30 seconds), touches only the spreadsheet or document itself, needs no external services, and runs for whoever is editing. onEdit flagging a cell status, onOpen adding a custom menu — these are the right fits.

Use an installable trigger when: you need GmailApp, CalendarApp, UrlFetchApp, or any service requiring authorization; when the operation might take more than a few seconds; or when the trigger must fire even if no one is actively in the file (time-based triggers are always installable). The 6-minute execution limit replaces the 30-second wall.

One pattern I keep coming back to: install the trigger under a dedicated service account or a shared team Google account, not someone's personal account. That way the quota is predictable, the authorization doesn't vanish when a person leaves, and you can audit the trigger list from a single identity. ScriptApp.newTrigger() lets you register it programmatically so there's a code record of what's installed and when.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why does my onEdit function work when I run it manually but do nothing on edit?
When you run it manually, you are the active user and the function has your auth context. When it fires as a simple trigger on an actual edit, it has no auth and cannot call services like GmailApp or external URLs. If it also throws an error, simple triggers do not surface errors to the user — check View > Logs or the Apps Script execution transcript.
Can I have more than one installable trigger for the same event?
Yes. You can register multiple onFormSubmit or onChange installable triggers on the same spreadsheet. They all fire, but order is not guaranteed. Each execution counts against the installing user's quota separately, so stacking triggers on high-volume forms adds up fast.
What happens to installable triggers when the file is copied?
They do not copy. The triggers are bound to the original file and the original installer. A copy of the spreadsheet has no active triggers; you have to re-register them from the new file's script editor.
How do I see which installable triggers are currently registered?
Open the Apps Script editor, click the clock icon (Triggers) in the left sidebar. That list shows every registered trigger, the function it calls, the event source, and the owner. ScriptApp.getProjectTriggers() returns the same list programmatically if you want to log or audit them from a script.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.