Skip Navigation
Show nav
Heroku Dev Center Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
Heroku Dev Center Dev Center
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log in or Sign up
View categories

Categories

  • Heroku Architecture
    • Compute (Dynos)
      • Dyno Management
      • Dyno Concepts
      • Dyno Behavior
      • Dyno Reference
      • Dyno Troubleshooting
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Buildpacks
    • Platform Principles
  • Developer Tools
    • AI Tools
    • Command Line
    • Heroku VS Code Extension
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery & Integration (Heroku Flow)
    • Continuous Integration
  • Language Support
    • Node.js
      • Working with Node.js
      • Node.js Behavior in Heroku
      • Troubleshooting Node.js Apps
    • Ruby
      • Rails Support
        • Working with Rails
      • Working with Bundler
      • Working with Ruby
      • Ruby Behavior in Heroku
      • Troubleshooting Ruby Apps
    • Python
      • Working with Python
      • Background Jobs in Python
      • Python Behavior in Heroku
      • Working with Django
    • Java
      • Java Behavior in Heroku
      • Working with Java
      • Working with Maven
      • Working with Spring Boot
      • Troubleshooting Java Apps
    • PHP
      • Working with PHP
      • PHP Behavior in Heroku
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
    • .NET
      • Working with .NET
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
      • Migrating to Heroku Postgres
    • Heroku Key-Value Store
    • Apache Kafka on Heroku
    • Other Data Stores
  • AI
    • Inference Essentials
    • Inference API
    • Inference Quick Start Guides
    • AI Models
    • Tool Use
    • AI Integrations
    • Vector Database
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
      • Single Sign-on (SSO)
    • Private Spaces
      • Infrastructure Networking
    • Compliance
  • Heroku Enterprise
    • Enterprise Accounts
    • Enterprise Teams
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
    • Heroku AppLink
      • Working with Heroku AppLink
      • Heroku AppLink Reference
      • Getting Started with Heroku AppLink
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
    • Other Salesforce Integrations
  • Add-ons
  • All Add-ons
  • Advanced Scheduler

Advanced Scheduler

Table of Contents [expand]

  • Provisioning the add-on
  • Quick Start (5 minutes)
  • Defining tasks
  • Testing tasks
  • Scheduling tasks
  • Debugging tasks
  • Execution Logs
  • Long-running tasks
  • Dyno Type
  • Concurrent one-off dyno limits
  • Dashboard
  • Service API
  • Heroku CLI Plugin
  • Task monitoring
  • Tasks timing out
  • Import Heroku Scheduler Jobs
  • Advanced Scheduler and Container Registry
  • Migrating between plans
  • Removing the add-on
  • Support

Last updated February 17, 2026

Advanced Scheduler

This add-on is operated by MISC CO BV

Reliable and powerful task scheduling as a service.

Advanced Scheduler is an add-on that provides task scheduling as a service on Heroku using one-off dynos and cron expressions.

The service is built on the same principles as Heroku Scheduler, and it distinguishes itself by providing greater flexibility, transparency, and reliability in a cost-effective way.

All features are accessible from the Advanced Scheduler dashboard. It allows you to configure triggers that execute your tasks once on a specific date and time, or at a certain time interval. When executed, these tasks run in one-off dynos and show up in your application’s logs. This service is language-agnostic, meaning it can be integrated regardless of the programming language you use.

Like Heroku Scheduler, Advanced Scheduler executes scheduled tasks via one-off dynos that will count towards your monthly usage.

 

Advanced Scheduler depends on the Heroku Platform API for task execution. If the API is unavailable, execution of your scheduled tasks can be missed. If scheduled tasks are a critical component of your application, it is recommended to run a custom clock process instead.

Provisioning the add-on

Advanced Scheduler can be attached to a Heroku application via the Heroku CLI:

A list of all plans available can be found here.

$ heroku addons:create advanced-scheduler -a sharp-mountain-4005
-----> Adding advanced-scheduler to sharp-mountain-4005... done, v18 (free)

After you install Advanced Scheduler, your application should be configured to fully integrate with the add-on. If you are new to task scheduling, you can read up on defining, testing, scheduling and debugging tasks below.

Quick Start (5 minutes)

This guide walks you through installing Advanced Scheduler, defining a task, running it once, and scheduling it — all in about 5 minutes.

If you need help while getting started or run into any issues, see Support for available support channels.

1. Install the add-on

Attach Advanced Scheduler to your Heroku application using the CLI:

$ heroku addons:create advanced-scheduler -a sharp-mountain-4005

Once installed, the add-on is immediately available for your application.

2. Define a task

A task is any command that can be executed inside your application. This can be a framework-specific task (for example a rake task), a script in bin/, or a process type defined in your Procfile.

If you have not defined a task yet, see Defining tasks for framework- and language-specific examples.

Make sure your task:

  • Runs to completion without user interaction and does not run indefinitely
  • Exits with status code 0 on success

3. Test the task on Heroku

Before scheduling your task, verify that it runs correctly on Heroku by executing it manually:

$ heroku run <your-task>

Example:

$ heroku run node send-reminders.js

If the task fails here, fix it before continuing.

4. Schedule the task

Open the Advanced Scheduler dashboard:

$ heroku addons:open advanced-scheduler -a sharp-mountain-4005

Create a new trigger and configure:

  1. The command to execute (or a Procfile process type)
  2. Whether the task should run once at a specific date and time or on a recurring schedule
  3. (Optional) Timeout and dyno type

New triggers are active by default. For more information on trigger configuration, see Scheduling tasks.

5. Verify execution and logs

When the trigger runs, the task executes in a one-off dyno and writes logs to your application logs.

To view logs in real time using the CLI:

$ heroku logs -t -d advanced-scheduler -a sharp-mountain-4005

You can also view execution logs in the Advanced Scheduler dashboard. See Debugging tasks for troubleshooting guidance.

If you run into any issues or have questions while getting started, do not hesitate to reach out through one of the available support channels. See Support for more information.

Next steps

  • Monitor task executions and failures: Task monitoring
  • Run longer jobs safely: Long-running tasks
  • Import existing jobs from Heroku Scheduler: Import Heroku Scheduler Jobs
  • Automate trigger management via API: Service API
  • Create and manage triggers via CLI: Heroku CLI Plugin

Defining tasks

Tasks are any command that can be run in your application.

Almost every framework or language has a convention to define such a command. Some have a built-in feature for setting up tasks, while others require you to add a script to bin/ that will perform the task.

Note that each framework or language might define tasks in a different way. Refer to the best practices for the framework or language you are using inside your application.

Advanced Scheduler is language-agnostic, meaning it works with any language or framework supported on Heroku. The examples below cover the most common languages and frameworks.

Regardless of the language or framework you use, make sure your task runs to completion without user interaction, does not run indefinitely, and exits with status code 0 on success and a non-zero code on failure.

Node.js

In Node.js, the recommended approach is to create a script in your bin/ directory. Always handle errors explicitly and exit with the correct exit code.

Create a script at bin/send-reminders.js:

#!/usr/bin/env node

'use strict';

const db = require('../lib/db');

let connected = false;
let isShuttingDown = false;

async function shutdown(exitCode) {
  if (isShuttingDown) return;
  isShuttingDown = true;
  if (connected) {
    try {
      await db.close();
    } catch (err) {
      console.warn('Warning: db.close() failed during shutdown:', err.message);
    }
  }
  process.exitCode = exitCode;
}

// After an uncaughtException the process heap may be corrupted.
// Perform no async work — force exit immediately.
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception — exiting immediately:', err);
  process.exit(1);
});

// An unhandledRejection in a one-off task script is always fatal.
process.on('unhandledRejection', (reason) => {
  console.error('Unhandled rejection:', reason);
  shutdown(1);
});

(async () => {
  try {
    console.log('Sending reminders...');
    await db.connect();
    connected = true;
    await db.sendReminders();
    console.log('done.');
    await shutdown(0);
  } catch (err) {
    console.error('Task failed:', err.message);
    await shutdown(1);
  }
})();

Schedule this task using the command:

node bin/send-reminders.js

Alternatively, define the task as a script in your package.json and schedule it using npm run:

{
  "scripts": {
    "send-reminders": "node bin/send-reminders.js"
  }
}
npm run send-reminders

Close all open resources (database connections, timers) and let Node exit naturally. Prefer process.exitCode over process.exit() in normal control flow — process.exit() forcibly terminates the event loop before pending I/O or logs finish flushing. The exception is inside uncaughtException handlers, where the process is already in an unsafe state and must be exited explicitly with process.exit().

Python

In Python, you can use a standalone script or a framework-specific approach.

Standalone script

Create a script at scripts/send_reminders.py:

#!/usr/bin/env python3

import sys

def main() -> int:
    try:
        print('Sending reminders...')
        # your task logic here
        print('done.')
        return 0
    except Exception as e:
        print(f'Task failed: {e}', file=sys.stderr)
        return 1

if __name__ == '__main__':
    raise SystemExit(main())

Schedule this task using the command:

python scripts/send_reminders.py

Django management command

For Django applications, the recommended approach is to create a custom management command. Create the file myapp/management/commands/send_reminders.py:

from django.core.management.base import BaseCommand, CommandError
from myapp.mailer import send_reminders

class Command(BaseCommand):
    help = 'Send reminders to users'

    def handle(self, *args, **options):
        try:
            self.stdout.write('Sending reminders...')
            send_reminders()
            self.stdout.write('done.')
        except Exception as e:
            raise CommandError(f'Task failed: {e}') from e

Schedule this task using the command:

python manage.py send_reminders

Flask CLI command

For Flask applications, register a custom CLI command inside your application factory:

# app/__init__.py
import sys
import click
from flask import Flask

def create_app():
    app = Flask(__name__)

    @app.cli.command('send-reminders')
    def send_reminders_command():
        """Send reminders to users."""
        try:
            click.echo('Sending reminders...')
            # your task logic here
            click.echo('done.')
        except Exception as e:
            click.echo(f'Task failed: {e}', err=True)
            sys.exit(1)

    return app

Schedule this task using the command:

flask --app app:create_app send-reminders

PHP

In PHP, you can use a standalone script or a framework-specific approach.

Standalone script

Create a script at scripts/send-reminders.php:

<?php

// If your task uses Composer packages, uncomment:
// require __DIR__ . '/../vendor/autoload.php';

$exitCode = 0;

try {
    echo "Sending reminders...\n";
    // your task logic here
    echo "done.\n";
} catch (Throwable $e) {
    fwrite(STDERR, "Task failed: {$e->getMessage()}\n");
    $exitCode = 1;
}

exit($exitCode);

Schedule this task using the command:

php scripts/send-reminders.php

Laravel Artisan command

For Laravel applications, create a custom Artisan command. Generate the command using:

$ php artisan make:command SendReminders

This creates app/Console/Commands/SendReminders.php. Update it to fit your needs:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\Mailer;

class SendReminders extends Command
{
    protected $signature   = 'reminders:send';
    protected $description = 'Send reminders to users';

    public function handle(Mailer $mailer): int
    {
        try {
            $this->info('Sending reminders...');
            $mailer->sendReminders();
            $this->info('done.');
            return Command::SUCCESS;
        } catch (\Throwable $e) {
            $this->error("Task failed: {$e->getMessage()}");
            return Command::FAILURE;
        }
    }
}

Schedule this task using the command:

php artisan reminders:send

Symfony console command

For Symfony applications, create a console command in src/Command/SendRemindersCommand.php:

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:send-reminders', description: 'Send reminders to users')]
class SendRemindersCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            $output->writeln('Sending reminders...');
            // task logic here
            $output->writeln('done.');
            return Command::SUCCESS;
        } catch (\Throwable $e) {
            $output->writeln("<error>Task failed: {$e->getMessage()}</error>");
            return Command::FAILURE;
        }
    }
}

Schedule this task using the command:

php bin/console app:send-reminders

Ruby

In Ruby, the convention is to define rake tasks or create a standalone script in bin/.

Rake task

For Rails applications, copy the code below into lib/tasks/scheduler.rake and customize it to fit your needs:

desc "This task is called by the Advanced Scheduler add-on"
task :send_reminders => :environment do
  puts "Sending reminders..."
  User.send_reminders
  puts "done."
end

Schedule this task using the command:

bundle exec rake send_reminders

Standalone script

An example bin/send-reminders script:

#!/usr/bin/env ruby

require_relative '../lib/mailer'

begin
  puts 'Sending reminders...'
  Mailer.send_reminders
  puts 'done.'
  exit 0
rescue => e
  $stderr.puts "Task failed: #{e.message}"
  $stderr.puts e.backtrace.join("\n")
  exit 1
end

Java

In Java, the recommended approach on Heroku is to compile your application into an executable JAR and invoke a main class that performs your task.

Spring Boot ApplicationRunner

For Spring Boot applications, use an ApplicationRunner to execute your task and then shut down:

package com.example.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SendRemindersApplication implements ApplicationRunner {

    private static final Logger logger =
        LoggerFactory.getLogger(SendRemindersApplication.class);

    private final ReminderService reminderService;

    public SendRemindersApplication(ReminderService reminderService) {
        this.reminderService = reminderService;
    }

    public static void main(String[] args) {
        System.exit(
            SpringApplication.exit(
                SpringApplication.run(SendRemindersApplication.class, args)
            )
        );
    }

    @Override
    public void run(ApplicationArguments args) {
        logger.info("Sending reminders...");
        reminderService.sendReminders();
        logger.info("done.");
    }
}

Build the JAR and schedule this task using the command:

java -jar target/send-reminders.jar

Always wrap SpringApplication.run() with SpringApplication.exit() and pass the result to System.exit(). Without this, background threads in the Spring context will keep the JVM — and your one-off dyno — running indefinitely.

Executable JAR with a main class

For non-Spring applications, create a class with a main method:

package com.example;

public class SendReminders {

    public static void main(String[] args) {
        try {
            System.out.println("Sending reminders...");
            // task logic here
            System.out.println("done.");
            System.exit(0);
        } catch (Exception e) {
            System.err.println("Task failed: " + e.getMessage());
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }
}

Schedule this task using the command:

java -cp target/myapp.jar com.example.SendReminders

Go

In Go, compile your task into a binary by placing it under cmd/ in your repository. Reference the compiled binary in your Procfile so Heroku knows how to run it.

Create a task at cmd/send-reminders/main.go:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/example/myapp/internal/mailer"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
    defer stop()

    if err := run(ctx); err != nil {
        log.Printf("ERROR: %v", err)
        os.Exit(1)
    }
}

func run(ctx context.Context) error {
    fmt.Println("Sending reminders...")
    if err := mailer.SendReminders(ctx); err != nil {
        return fmt.Errorf("send reminders: %w", err)
    }
    fmt.Println("done.")
    return nil
}

Once built, reference the binary in your Procfile. The output path depends on your build setup — check where your buildpack or build script places the compiled binary:

send-reminders: <path-to-binary>

Schedule this task using the process type name:

send-reminders

Scala

In Scala, use sbt-assembly to build a fat JAR and define a main object for your task.

Add the plugin to project/plugins.sbt:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.2.0")

Set a stable jar name in build.sbt so the Procfile path is predictable:

assembly / assemblyJarName := "myapp-assembly.jar"

Create a task object at src/main/scala/com/example/tasks/SendReminders.scala:

package com.example.tasks

object SendReminders {

  def main(args: Array[String]): Unit = {
    try {
      println("Sending reminders...")
      Mailer.sendReminders()
      println("done.")
      sys.exit(0)
    } catch {
      case e: Exception =>
        System.err.println(s"Task failed: ${e.getMessage}")
        e.printStackTrace(System.err)
        sys.exit(1)
    }
  }
}

Build the JAR with sbt assembly, then define the process type in your Procfile. The JAR path varies based on your Scala version and project name — check the target/ directory after building.

send-reminders: java -cp target/scala-2.13/myapp-assembly.jar com.example.tasks.SendReminders

Schedule this task using the process type name:

send-reminders

Clojure

In Clojure, use Leiningen to define your task as a namespace with a -main function and run it from an uberjar for fast startup.

Create a task namespace at src/myapp/tasks/send_reminders.clj:

(ns myapp.tasks.send-reminders
  (:require [myapp.mailer :as mailer])
  (:gen-class))

(defn -main [& _args]
  (try
    (println "Sending reminders...")
    (mailer/send-reminders!)
    (println "done.")
    (System/exit 0)
    (catch Exception e
      (binding [*out* *err*]
        (println (str "Task failed: " (.getMessage e))))
      (System/exit 1))))

Build the uberjar and schedule this task using the command:

java -cp target/myapp-standalone.jar clojure.main -m myapp.tasks.send-reminders

You can also define an alias in project.clj to simplify the command:

:aliases {"send-reminders" ["run" "-m" "myapp.tasks.send-reminders"]}
lein send-reminders

Always call (System/exit 0) or (System/exit 1) explicitly in Clojure tasks. Without it, background threads in the Clojure runtime will keep the JVM — and your one-off dyno — running indefinitely.

Testing tasks

Once you have written your task and see that it is functioning locally, the next step is to deploy your application and test your task on Heroku.

To do so, use heroku run to run your task on Heroku:

$ heroku run <your-task>

Scheduling tasks

To schedule a task, you need to create a new active trigger for it. Triggers are configured using the Advanced Scheduler dashboard.

Enter the task and specify if the trigger needs to be executed only once or at a certain time interval. For one-off triggers, define a date and time at which the task should be executed. For recurring triggers, provide a standard cron expression or use the schedule helper to define the interval. As a tip, check your cron expressions here to verify they are correct. By default, a new trigger will be activated directly after creation. If you do not want this behaviour, you can configure the trigger to stay inactive.

Instead of specifying a command, you can specify one of the process types in your app’s Procfile. The command associated with the process type will then be executed, together with any parameters you supply. See the syntax for one-off dynos to learn more.

Note that for each plan there are limits on the total number of tasks that can be scheduled at one point in time, as well as the total number of task executions in one month. Once one of these limits is exceeded, attempts to schedule a new task will fail.

Debugging tasks

To debug a task, you have to check your application’s logs. You can check your logs in real time in the Advanced Scheduler dashboard, use any of the logging add-ons available in the Heroku Elements marketplace or use the Heroku CLI.

To check you real-time logs in the Advanced Scheduler dashboard, navigate to the Execution Logs page by clicking the View Execution Logs button in the top navigation bar on the overview page.

To check your real-time logs with the Heroku CLI, use the heroku logs command:

$ heroku logs -t -a <your-app> -d advanced-scheduler
2020-01-15T14:10:16+00:00 heroku[advanced-scheduler.1]: State changed from created to starting
2020-01-15T14:10:16+00:00 app[advanced-scheduler.1]: Starting process with command `node bin/send-newsletter.js`
2020-01-15T14:10:19+00:00 app[advanced-scheduler.1]: Sending newsletters...
2020-01-15T14:10:27+00:00 app[advanced-scheduler.1]: done.
2020-01-15T14:10:28+00:00 heroku[advanced-scheduler.1]: State changed from up to complete

A running task is also visible with the heroku ps command:

$ heroku ps
=== advanced-scheduler (Free): node bin/send-newsletter.js (1)
advanced-scheduler.1: up 2020/01/15 14:10:16 +0100 (~ 2s ago)

The task monitoring of Advanced Scheduler depends on the result of your task. Make sure your task exits with the right exit code (sometimes referred to as a return status or exit status). A successful task returns a 0, while an unsuccessful one returns a non-zero value.

Execution Logs

This feature is currently in Beta.

Advanced Scheduler can be configured to use Heroku’s log drains to capture and view the application logs from the one-off dynos that run your tasks. This feature is useful for testing new triggers or debugging failed task executions in real time.

By default, no log drain is set up. To enable the log drain, click the View Execution Logs button in the Advanced Scheduler dashboard, click Enable Log Drain… and confirm.

Advanced Scheduler only processes logs from one-off dynos that it starts. Other logs are omitted. Logs are not persisted to a database.

When you enable the log drain for your Heroku app, Advanced Scheduler appends the trigger UUID to the process type of the one-off dynos it starts. As a result, the dyno name will change from advanced-scheduler.1234 to advanced-scheduler-<trigger-uuid>.1234. This allows you to easily filter logs by trigger. If you disable the log drain, the trigger UUID will no longer be included in the dyno name.

For production-ready log persistence, we recommend using one of Heroku Elements’ logging add-ons with your Heroku app.

Long-running tasks

Although it is generally recommended to keep tasks lightweight and quick to execute, Advanced Scheduler can be used for longer-running tasks. Make sure to only have 1 task running by setting the execution interval higher than the task’s maximum execution time. In the event that a specific task does run longer than intended, you can force the task to exit by setting its trigger’s timeout value to the maximum allowed execution time. See tasks timing out to learn more.

Note that a task can run for up to 24 hours by settings its trigger’s timeout value to 86400 seconds.

Dyno Type

When configuring a trigger, select a dyno type available within the dyno tier configured for your Heroku app. In other words, you can only use for example standard-2x for your one-off dynos when you use the Professional tier for your formation dynos.

Heroku offers the following dyno tiers:

  • Eco tier: An app that uses eco dynos can only use eco dynos for its one-off dynos.
  • Basic tier: An app that uses basic dynos can only use basic dynos for its one-off dynos.
  • Professional tier: An app that uses Professional-tier dynos (standard and performance) can use standard-1x, standard-2x, performance-m, performance-l, performance-l-ram, performance-xl and performance-2xl for its one-off dynos.
  • Private tier: An app that uses Private-tier dynos can use private-s, private-m, private-l, private-l-ram, private-xl and private-2xl for its one-off dynos.
  • Shield tier: An app that uses Shield-tier dynos can use shield-s, shield-m, shield-l, shield-l-ram, shield-xl and shield-2xl for its one-off dynos.

When a trigger is configured to use a type of dyno that is not available for your Heroku app, Advanced Scheduler will automatically fall back to Heroku’s default dyno type for your app.

Concurrent one-off dyno limits

The limit for your app’s concurrently running one-off dynos depends on several factors. See which limit applies to your situation.

Beware that when exceeding your app’s concurrent one-off dyno limit, the next task might not be executed.

To stay below Heroku’s concurrent one-off dyno limit, make sure to plan the execution of your tasks with care. Avoid a backlog of one-off dynos created when scheduled tasks are executed before running tasks are finished or time out.

Alternatively, you can contact Heroku to get your limit raised.

Dashboard

The Advanced Scheduler dashboard allows you to configure one-off and recurring triggers that execute different tasks.

You can access the dashboard via the CLI:

$ heroku addons:open advanced-scheduler
Opening advanced-scheduler for sharp-mountain-4005

or by visiting the Heroku Dashboard and selecting the application in question. Select Advanced Scheduler from the Add-ons menu.

Service API

The Advanced Scheduler Service API lets you programmatically automate, extend and combine Advanced Scheduler with other services. You can use the Service API to create and manage triggers. For details, see the Service API Reference.

To interact with the Service API, you will need an API token. This API token can be generated in the Advanced Scheduler dashboard.

Note that when generating an API token in the Advanced Scheduler dashboard, the ADVANCED_SCHEDULER_API_TOKEN config var will be set on your Heroku application and cause it to restart.

Heroku CLI Plugin

Advanced Scheduler provides an Heroku CLI plugin to create and manage triggers directly from the terminal using the Heroku CLI. For details, see the Advanced Scheduler CLI Reference.

To install the plugin:

$ heroku plugins:install advanced-scheduler

To start using the plugin:

$ heroku triggers --app example

 === 01234567-89ab-cdef-0123-456789abcdef (active): Monday morning newsletter
 At 09:00 AM, only on Monday (UTC) w/ Standard-1X ⬢
 $ node bin/send-newsletter.js

To consult the plugin documentation:

$ heroku triggers -h

List the Advanced Scheduler triggers for an app

USAGE
  $ heroku triggers...

OPTIONS
  -a, --app=app  (required) app to run command against
  -h, --help     show CLI help
  -j, --json     output triggers in json format

EXAMPLE
  $ heroku triggers -a example

COMMANDS
  triggers:activate    Activate an Advanced Scheduler trigger for an app
  triggers:create      Create a new Advanced Scheduler trigger for an app
  triggers:deactivate  Deactivate an Advanced Scheduler trigger for an app
  triggers:delete      Permanently delete an Advanced Scheduler trigger for an app
  triggers:update      Update an Advanced Scheduler trigger for an app

Task monitoring

By default, Advanced Scheduler monitors the executions of your scheduled tasks. For every trigger an email notification is sent on the first failed execution each day.

Note that the successful or failed execution of your task depends on the process exit code (sometimes referred to as a return status or exit status), so make sure your process is exiting properly. When the process exits with code 0, the execution is considered successful. Anything else is treated as a failed execution.

 

Whenever a one-off dyno fails to be provisioned, the process never actually runs and consequently there is no process exit code. Advanced Scheduler interprets this as a failed execution and sends a task failure alert notification with exit status null.

Alert Notification Subscribers

By default, the distribution for email notifications is to all app owners and collaborators for non-org accounts, and admins for people in a Heroku Enterprise org. Alternatively, reach out to support@advancedscheduler.io to add additional email addresses, such as for email-based PagerDuty integration.

Tasks timing out

When a task runs longer than its trigger’s timeout value, it will be forced to exit shortly after. This mechanism is intended to either prevent a backlog of one-off dynos or to ensure only 1 task of a specific trigger is running at a time. Always aim to make tasks finish by themselves. When a task does time out, make sure to figure out why and take action to prevent it from happening again.

Advanced Scheduler supports getting alert notifications on task timeout. Opt-in by reaching out to support@advancedscheduler.io.

 

Note that the default timeout value for new triggers is 1800 seconds or 30 minutes and the maximum timeout value is 86400 seconds or 24 hours.

Import Heroku Scheduler Jobs

Heroku Scheduler jobs can be imported into Advanced Scheduler using the Advanced Scheduler dashboard. Advanced Scheduler is totally compatible with Heroku Scheduler. All imported jobs will behave exactly the same after import.

To start importing your Heroku Scheduler jobs, head to the Advanced Scheduler dashboard and click the Import Heroku Scheduler Jobs button in the Triggers section of the overview page.

All imported jobs will be inactive to avoid collisions with your existing Heroku Scheduler jobs.

Heroku API token

Advanced Scheduler uses a Heroku API token to access the Heroku Scheduler jobs on your Heroku app. The Heroku API token can be retrieved using the heroku auth:token command, which outputs your current Heroku CLI authentication token and when it will expire. The token can be explicitly invalidated by running heroku auth:logout. For more information check out the Heroku CLI documentation.

The provided Heroku API token will not be stored by Advanced Scheduler and will only be used to perform a single GET request to fetch your Heroku Scheduler jobs.

Heroku Scheduler Timeouts

All jobs run by Heroku Scheduler have a maximum runtime equal to the frequency of their execution. For example, jobs scheduled to run every 10 minutes will be terminated after approximately 10 minutes. To make sure imported jobs behave identically, Advanced Scheduler uses the same timeout values that Heroku Scheduler uses.

Advanced Scheduler and Container Registry

If you are using Advanced Scheduler and Container Registry as your deployment method, your task must be accessible from the web image. There is no way to specify a non-web image for task execution.

Migrating between plans

Note that application owners should carefully manage the migration timing to ensure proper application function during the migration process.

Use the heroku addons:upgrade command to migrate to a new plan.

$ heroku addons:upgrade advanced-scheduler:newplan
-----> Upgrading advanced-scheduler:newplan to sharp-mountain-4005... done, v18 ($60/mo)
       Your plan has been updated to: advanced-scheduler:newplan

When downgrading to a plan that does not include access to the Advanced Scheduler Service API, the API token will be removed if applicable. This action will also remove the config var ADVANCED_SCHEDULER_API_TOKEN and cause your Heroku application to restart.

Removing the add-on

You can remove Advanced Scheduler via the CLI:

This will destroy all associated data and cannot be undone!

$ heroku addons:destroy advanced-scheduler
-----> Removing advanced-scheduler from sharp-mountain-4005... done, v20 (free)

Support

All Advanced Scheduler support and runtime issues should be submitted via one of the Heroku Support channels. Any non-support related issues or product feedback is welcome at support@advancedscheduler.io.

Feedback

Log in to submit feedback.

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2026 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices