fx Triggers & Automation

Installable and time-driven triggers

⏱ 15 min

What you'll learn

  • How installable triggers differ from simple ones
  • Creating triggers from the Triggers page and from code
  • Time-driven triggers: hourly, daily, weekly
  • Avoiding duplicate triggers

Concept

Simple vs installable

Simple trigger Installable trigger
Setup Just name the function Create it once (UI or code)
Can send email / use Drive No Yes
Runs as The user at that moment The person who created the trigger
Time-based No Yes
Max run time 30 seconds ~6 minutes
Event types open, edit, selection open, edit, change, form submit, time, calendar

Installable triggers run with your permissions, even when nobody has the file open — that's why they can send emails at 9 a.m. every day.

Creating a trigger from the editor

  1. Click the Triggers (clock) icon on the left.
  2. + Add Trigger (bottom right).
  3. Choose the function, event source (Time-driven or From spreadsheet), and the type.
  4. Optionally set failure notifications — choose Notify me immediately for important jobs.
  5. Save and authorize.

Creating a trigger from code

// Run dailyReport every day between 9 and 10 a.m. (project time zone)
function createDailyTrigger() {
  ScriptApp.newTrigger('dailyReport')
    .timeBased()
    .everyDays(1)
    .atHour(9)
    .create();
}

// Run syncRates every hour
function createHourlyTrigger() {
  ScriptApp.newTrigger('syncRates')
    .timeBased()
    .everyHours(1)
    .create();
}

// Run weeklySummary every Monday around 8 a.m.
function createWeeklyTrigger() {
  ScriptApp.newTrigger('weeklySummary')
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(8)
    .create();
}

Run the create… function once. Each run adds another trigger, so running it twice gives you two emails every morning.

Note: atHour(9) means "sometime between 9:00 and 10:00", not exactly 9:00. Minute-based triggers allow everyMinutes(1), 5, 10, 15 or 30.

Installable onEdit (with email)

function createEditTrigger() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  ScriptApp.newTrigger('onBigOrder')
    .forSpreadsheet(ss)
    .onEdit()
    .create();
}

function onBigOrder(e) {
  const r = e.range;
  if (r.getSheet().getName() !== 'Orders' || r.getColumn() !== 4) return;
  if (Number(e.value) > 100000) {
    MailApp.sendEmail('owner@example.com', 'Big order', `Order in row ${r.getRow()}: ₹${e.value}`);
  }
}

Give the handler a different name than onEdit — otherwise it runs twice (once as a simple trigger, once as installable), and the simple one fails on MailApp.

Safe setup: avoid duplicates

function resetTriggers() {
  // Delete existing triggers for this project that call dailyReport
  ScriptApp.getProjectTriggers()
    .filter(t => t.getHandlerFunction() === 'dailyReport')
    .forEach(t => ScriptApp.deleteTrigger(t));

  ScriptApp.newTrigger('dailyReport').timeBased().everyDays(1).atHour(9).create();
}

Run resetTriggers whenever you want to (re)install — it always leaves exactly one.

Key points

  • Installable triggers can email, use Drive and run on a schedule.
  • They run as the creator, so they work while everyone is offline.
  • Create them once; use getProjectTriggers to clean up duplicates.
  • Check Executions to see each triggered run.

Exercises

mediumPractice 1

Write resetTriggers for a function remindPending that runs every weekday (Mon–Fri) at about 10 a.m. Hint: create five onWeekDay triggers in a loop.

Quiz

Which trigger can send an email? a) Simple onEdit b) Installable onEdit c) Custom function
Installable onEdit
What happens if you run createDailyTrigger three times? a) Nothing extra b) Three daily triggers exist c) An error
Three daily triggers exist
Installable and time-driven triggers · Google Apps Script | ExcelWalaa