DEV Community

bulldo.gs
bulldo.gs

Posted on • Originally published at bulldo.gs

Run a script every day at a set time with Apps Script

Originally written for bulldo.gs — republished here with the canonical link pointing home.

I want to schedule a Google Apps Script function to run automatically at a specific time every day without having to trigger it manually.

// Run myDailyJob at 8 AM every day (script timezone).
// Call createDailyTrigger() once from the editor — never from a trigger.
function createDailyTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() === 'myDailyJob') {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
  ScriptApp.newTrigger('myDailyJob')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create();
}

function myDailyJob() {
  Logger.log('Running daily job at: ' + new Date());
}
Enter fullscreen mode Exit fullscreen mode

What atHour actually promises

Apps Script time-based triggers do not fire at a precise clock time. When you call atHour(8), you are telling the scheduler to run your function sometime between 8:00 and 9:00 AM in the script's timezone — not at exactly 08:00:00. Google's infrastructure batches trigger execution and spreads it across that window. If your job must run at exactly 8:05, Apps Script is the wrong tool; use Cloud Scheduler calling a Cloud Function instead.

The script's timezone is set per project, not per user. Go to Project Settings (the gear icon in the left sidebar) and check the Time zone field. If that reads America/New_York and your Sheets data is in UTC, every atHour call is offset accordingly. I've burned a morning debugging a report that always showed yesterday's data — the trigger was firing at what was effectively midnight UTC.

One practical consequence: if you chain two time-based triggers expecting a gap of exactly one hour between them, that gap can be anywhere from zero to two hours on a bad day. Build tolerance into any logic that depends on recency.

Deleting before creating: the duplicate trap

Every call to ScriptApp.newTrigger(...).create() registers a new trigger entry, regardless of whether one already exists for the same function. Re-running your setup function — say, after tweaking the hour — stacks a second trigger on top of the first. After three iterations you have three daily runs, and the Triggers dashboard in the Apps Script editor is the only place that shows you this.

The fix is to delete any existing triggers for the target function before creating a new one, which is what the loop in the snippet above does. getHandlerFunction() returns the string name you passed to newTrigger(), so matching on 'myDailyJob' is exact.

A common mistake is calling the setup function from another trigger — say, a daily cleanup job that reinitializes everything. That creates a new trigger every day while the previous one persists. createDailyTrigger() should be called exactly once, manually, from the editor's Run menu.

Authorization and the first-run prompt

Time-based triggers run as the user who authorized the script. When you run createDailyTrigger() for the first time, Apps Script will prompt for OAuth consent if the script hasn't been authorized yet. You must complete that flow in the editor; the trigger will not run until authorization is granted.

If you later revoke access (via myaccount.google.com/permissions) or the OAuth token expires due to a password change or policy rotation, the trigger silently fails. You will see a failure email from apps-scripts-notify@google.com with a vague 'Authorization is required' message. The fix is to open the script, run any function manually to re-trigger the consent flow, and re-authorize.

Scripts attached to a Workspace account (G Suite / Google Workspace) may also hit domain administrator restrictions on which OAuth scopes are allowed. If your trigger stops firing after an org policy change, check the Admin console under Security > API controls before assuming the script is broken.

FAQ

Why did my script run twice today?

You have duplicate triggers. Open the Apps Script editor, click the clock icon (Triggers) in the left sidebar, and look for multiple entries pointing to the same handler function. Delete all but one. This happens when you run the setup function more than once without deleting the existing trigger first.

Can I make atHour fire at exactly 8:15 instead of somewhere in the 8 AM window?

No. atHour(8) gives you a one-hour window; Apps Script offers no minute-level precision for time-based triggers. For exact timing you need Cloud Scheduler (cron syntax, minute precision) calling an Apps Script deployment via its web app URL, or a Cloud Function.

The trigger is listed but my function never runs — what's wrong?

Check three things in order: (1) the function name string passed to newTrigger() matches the actual function name exactly, case included; (2) the script is authorized — run it manually once to confirm no auth prompt appears; (3) look at the trigger execution log (Executions in the left sidebar) for error messages, which are often more specific than the failure email.

How do I set the timezone the trigger uses?

In the Apps Script editor, go to Project Settings (gear icon) and set the Time zone field. This is a per-project setting and affects all time-based triggers in that script. It is separate from the spreadsheet's locale timezone if this script is container-bound to a Sheet.


Want the plain-English version? Describe the automation at bulldo.gs and get working Apps Script back — free, no login.

Top comments (0)