// Apps Script

Delete all triggers for a project with Apps Script.

How to delete every installable trigger in an Apps Script project using ScriptApp.getProjectTriggers() and deleteTrigger() — fixes the 20-trigger limit and clears duplicate triggers left by re-run setup functions.

I need to delete all triggers in my Apps Script project because my setup function keeps creating duplicates every time I run it, and I've hit the 20-trigger-per-script cap.

The script

copy · paste · trigger
triggers.gs
Apps Script
// Delete every installable trigger attached to this project.
// Safe to run multiple times — stops when none remain.
function deleteAllTriggers() {
  const triggers = ScriptApp.getProjectTriggers();
  if (triggers.length === 0) {
    Logger.log('No triggers found.');
    return;
  }
  for (const trigger of triggers) {
    ScriptApp.deleteTrigger(trigger);
  }
  Logger.log('Deleted ' + triggers.length + ' trigger(s).');
}

// Re-install a single daily trigger after clearing the old ones.
function resetDailyTrigger() {
  deleteAllTriggers();
  ScriptApp.newTrigger('myDailyJob')
    .timeBased()
    .everyDays(1)
    .create();
}

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

Walkthrough

Why triggers pile up in the first place

Apps Script distinguishes between two trigger types: simple triggers (onOpen, onEdit) which are free and stateless, and installable triggers which are registered under your Google account and counted against a quota. Every call to ScriptApp.newTrigger().create() registers a new installable trigger, even if an identical one already exists.

The 20-trigger-per-script limit hits fast when you have a setup() function that you run during development, share with teammates, or call from onOpen to ensure the trigger is present. Each run adds one more entry. The dashboard in Apps Script (Extensions > Triggers) makes this visible, but clearing them by hand across dozens of rows is tedious enough that most people write the code instead.

The first time I hit this, it was a shared Sheet where four people had opened it, each triggering a fresh onOpen that called the installer. That's four time-based triggers for a job meant to run once a day.

How ScriptApp.getProjectTriggers() works

ScriptApp.getProjectTriggers() returns an array of Trigger objects scoped to the current script project, regardless of which Google account installed them — provided that account is yours. If a collaborator installed triggers under their own account, you will not see those; each owner can only manage their own triggers.

Each Trigger object carries enough metadata to identify it: getHandlerFunction() returns the function name it calls, getTriggerSource() tells you whether it's time-based or spreadsheet-event-based, and getUniqueId() gives a stable string ID. For a bulk delete you don't need any of that — you pass the object directly to ScriptApp.deleteTrigger().

The loop in the snippet above is intentionally simple. If you only want to delete triggers for a specific handler, filter first: triggers.filter(t => t.getHandlerFunction() === 'myDailyJob') before the delete loop. I keep that pattern in a utils file and import it into every project that has more than one scheduled job.

Pairing deletion with re-installation

The cleanest pattern is a resetDailyTrigger()-style wrapper: delete everything, then register exactly what you want. This makes the setup function idempotent — safe to run from onOpen, from a menu item, or from a CI deploy hook without accumulating stale entries.

One gotcha: ScriptApp.deleteTrigger() throws if the trigger was already deleted in the same execution (rare, but possible if you call deleteAllTriggers() twice in one run). The guard on triggers.length === 0 handles the empty case; if you're calling this from multiple code paths in the same session, wrap the delete in a try/catch or deduplicate by unique ID first.

Authorization matters here. The script must be authorized with the https://www.googleapis.com/auth/script.scriptapp OAuth scope. If you're running this in a new project and the scope isn't declared, Apps Script will prompt for it on first run. Container-bound scripts (attached to a Sheet or Doc) inherit it automatically when the project requests it.

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
Does ScriptApp.getProjectTriggers() delete triggers created by other users?
No. It only returns triggers installed under the account running the script. If a collaborator set up their own triggers, they must run the deletion under their own session.
Why do I still see old triggers in the dashboard after running deleteAllTriggers()?
The Apps Script triggers dashboard (Extensions > Triggers) sometimes caches stale entries. Refresh the page after running the function. If an entry persists, it may belong to a different authorized account or a different script project attached to the same spreadsheet.
How do I delete only triggers for one specific function, not all of them?
Filter before deleting: ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() === 'functionName').forEach(t => ScriptApp.deleteTrigger(t)). The getHandlerFunction() method returns the exact string name you passed to ScriptApp.newTrigger().
Will this work if my script is a library used by other projects?
No. ScriptApp inside a library call operates in the context of the library project, not the calling project. To manage triggers in the calling project, the trigger-management code must live in that project directly, not in the library.
// one good script a week

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