https://github.com/goforj/scheduler
Laravel-inspired job scheduler for Go with a fluent API, built on gocron.
https://github.com/goforj/scheduler
background-jobs builder-pattern cron developer-tools distributed-locking fluent-api go gocron goforj golang job-scheduler laravel-inspired redis scheduler task-scheduling
Last synced: 30 days ago
JSON representation
Laravel-inspired job scheduler for Go with a fluent API, built on gocron.
- Host: GitHub
- URL: https://github.com/goforj/scheduler
- Owner: goforj
- License: mit
- Created: 2025-12-17T09:04:25.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2025-12-18T22:26:35.000Z (about 2 months ago)
- Last Synced: 2025-12-20T22:51:19.637Z (about 2 months ago)
- Topics: background-jobs, builder-pattern, cron, developer-tools, distributed-locking, fluent-api, go, gocron, goforj, golang, job-scheduler, laravel-inspired, redis, scheduler, task-scheduling
- Language: Go
- Homepage:
- Size: 1.63 MB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
A fluent, Laravel-inspired scheduler for Go that wraps gocron with expressive APIs for defining, filtering, and controlling scheduled jobs.

## Features
- Fluent, chainable API for intervals, cron strings, and calendar helpers (daily/weekly/monthly).
- Overlap protection with optional distributed locking plus per-job tags and metadata.
- Filters (weekdays/weekends/time windows) and hooks (before/after/success/failure) keep jobs predictable.
- Command execution helper for running CLI tasks with background mode and env-aware tagging.
- Auto-generated, compile-tested examples ensure docs and behavior stay in sync.
## Why scheduler?
Go has excellent low-level scheduling libraries, but defining real-world schedules often turns into a maze of cron strings, conditionals, and glue code.
`scheduler` provides a Laravel-style fluent API on top of gocron that lets you describe **when**, **how**, and **under what conditions** a job should run - without hiding what’s actually happening.
Everything remains explicit, testable, and inspectable, while staying pleasant to read and maintain.
## Example
```go
scheduler.NewJobBuilder(s).
Name("reports:generate").
Weekdays().
Between("09:00", "17:00").
WithoutOverlapping().
DailyAt("10:30").
Do(func() {
generateReports()
})
```
## List jobs as an ASCII table
```go
package main
import (
"github.com/go-co-op/gocron/v2"
"github.com/goforj/scheduler"
)
func main() {
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
EveryMinute().
Name("cleanup").
Do(func() {})
scheduler.NewJobBuilder(s).PrintJobsList()
}
```
Example output:
```
+--------------------------------------------------------------------------------------+
| Scheduler Jobs › (3)
+----------------+----------+----------------+---------+--------+----------------------+
| Name | Type | Schedule | Handler | Next | Tags |
+----------------+----------+----------------+---------+--------+----------------------+
| hello:world | command | cron 0 0 * * 0 | - | in 3d | env=dev, args="w" |
| hello:world | command | every 1h | - | in 1h | env=dev, args="hour" |
| cleanup | function | every 1m | cleanup | in 1m | env=dev |
+----------------+----------+----------------+---------+--------+----------------------+
```
## Runnable examples
Every function has a corresponding runnable example under [`./examples`](./examples).
These examples are **generated directly from the documentation blocks** of each function, ensuring the docs and code never drift. These are the same examples you see here in the README and GoDoc.
An automated test executes **every example** to verify it builds and runs successfully.
This guarantees all examples are valid, up-to-date, and remain functional as the API evolves.
## API Index
| Group | Functions |
|------:|-----------|
| **Adapters** | [Lock](#lock) [Run](#run) [Unlock](#unlock) |
| **Commands** | [Command](#command) |
| **Concurrency** | [WithoutOverlapping](#withoutoverlapping) [WithoutOverlappingWithLocker](#withoutoverlappingwithlocker) |
| **Configuration** | [Timezone](#timezone) [WithCommandRunner](#withcommandrunner) [WithNowFunc](#withnowfunc) |
| **Construction** | [NewJobBuilder](#newjobbuilder) |
| **Diagnostics** | [CronExpr](#cronexpr) [Error](#error) [Job](#job) [PrintJobsList](#printjobslist) |
| **Execution** | [RunInBackground](#runinbackground) |
| **Filters** | [Between](#between) [Days](#days) [Environments](#environments) [Fridays](#fridays) [Mondays](#mondays) [Saturdays](#saturdays) [Skip](#skip) [Sundays](#sundays) [Thursdays](#thursdays) [Tuesdays](#tuesdays) [UnlessBetween](#unlessbetween) [Wednesdays](#wednesdays) [Weekdays](#weekdays) [Weekends](#weekends) [When](#when) |
| **Hooks** | [After](#after) [Before](#before) [OnFailure](#onfailure) [OnSuccess](#onsuccess) |
| **Locking** | [NewRedisLocker](#newredislocker) |
| **Metadata** | [JobMetadata](#jobmetadata) [Name](#name) |
| **Scheduling** | [Cron](#cron) [Daily](#daily) [DailyAt](#dailyat) [DaysOfMonth](#daysofmonth) [Do](#do) [Every](#every) [EveryFifteenMinutes](#everyfifteenminutes) [EveryFifteenSeconds](#everyfifteenseconds) [EveryFiveMinutes](#everyfiveminutes) [EveryFiveSeconds](#everyfiveseconds) [EveryFourHours](#everyfourhours) [EveryFourMinutes](#everyfourminutes) [EveryMinute](#everyminute) [EveryOddHour](#everyoddhour) [EverySecond](#everysecond) [EverySixHours](#everysixhours) [EveryTenMinutes](#everytenminutes) [EveryTenSeconds](#everytenseconds) [EveryThirtyMinutes](#everythirtyminutes) [EveryThirtySeconds](#everythirtyseconds) [EveryThreeHours](#everythreehours) [EveryThreeMinutes](#everythreeminutes) [EveryTwentySeconds](#everytwentyseconds) [EveryTwoHours](#everytwohours) [EveryTwoMinutes](#everytwominutes) [EveryTwoSeconds](#everytwoseconds) [Hourly](#hourly) [HourlyAt](#hourlyat) [Hours](#hours) [LastDayOfMonth](#lastdayofmonth) [Minutes](#minutes) [Monthly](#monthly) [MonthlyOn](#monthlyon) [Quarterly](#quarterly) [QuarterlyOn](#quarterlyon) [Seconds](#seconds) [TwiceDaily](#twicedaily) [TwiceDailyAt](#twicedailyat) [TwiceMonthly](#twicemonthly) [Weekly](#weekly) [WeeklyOn](#weeklyon) [Yearly](#yearly) [YearlyOn](#yearlyon) |
| **State management** | [RetainState](#retainstate) |
## Adapters
Lock invokes the underlying function.
```go
client := redis.NewClient(&redis.Options{})
locker := scheduler.NewRedisLocker(client, time.Minute)
lock, _ := locker.Lock(context.Background(), "job")
_ = lock.Unlock(context.Background())
```
Run executes the underlying function.
```go
runner := scheduler.CommandRunnerFunc(func(ctx context.Context, exe string, args []string) error {
return nil
})
_ = runner.Run(context.Background(), "echo", []string{"hi"})
```
Unlock invokes the underlying function.
## Commands
Command executes the current binary with the given subcommand and variadic args.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
Cron("0 0 * * *").
Command("jobs:purge", "--force")
```
## Concurrency
WithoutOverlapping ensures the job does not run concurrently.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
WithoutOverlapping().
EveryFiveSeconds().
Do(func() { time.Sleep(7 * time.Second) })
```
### WithoutOverlappingWithLocker
WithoutOverlappingWithLocker ensures the job does not run concurrently across distributed systems using the provided locker.
```go
locker := scheduler.LockerFunc(func(ctx context.Context, key string) (gocron.Lock, error) {
return scheduler.LockFunc(func(context.Context) error { return nil }), nil
})
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
WithoutOverlappingWithLocker(locker).
EveryMinute().
Do(func() {})
```
## Configuration
Timezone sets a timezone string for the job (not currently applied to gocron Scheduler).
```go
scheduler.NewJobBuilder(nil).
Timezone("America/New_York").
Daily()
```
WithCommandRunner overrides command execution (default: exec.CommandContext).
```go
runner := scheduler.CommandRunnerFunc(func(_ context.Context, exe string, args []string) error {
fmt.Println(exe, args)
return nil
})
builder := scheduler.NewJobBuilder(nil).
WithCommandRunner(runner)
fmt.Printf("%T\n", builder)
```
WithNowFunc overrides current time (default: time.Now). Useful for tests.
```go
fixed := func() time.Time { return time.Unix(0, 0) }
scheduler.NewJobBuilder(nil).WithNowFunc(fixed)
```
## Construction
NewJobBuilder creates a new JobBuilder with the provided scheduler.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
```
## Diagnostics
CronExpr returns the cron expression string configured for this job.
```go
builder := scheduler.NewJobBuilder(nil).Cron("0 9 * * *")
fmt.Println(builder.CronExpr())
```
Error returns the error if any occurred during job scheduling.
```go
builder := scheduler.NewJobBuilder(nil).DailyAt("bad")
fmt.Println(builder.Error())
```
Job returns the last scheduled gocron.Job instance, if available.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
b := scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
fmt.Println(b.Job() != nil)
```
PrintJobsList renders and prints the scheduler job table to stdout.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
EverySecond().
Name("heartbeat").
Do(func() {})
scheduler.NewJobBuilder(s).PrintJobsList()
```
## Execution
RunInBackground runs command/exec tasks in a goroutine.
```go
scheduler.NewJobBuilder(nil).
RunInBackground().
Command("noop")
```
## Filters
Between limits the job to run between the provided HH:MM times (inclusive).
```go
scheduler.NewJobBuilder(nil).
Between("09:00", "17:00").
EveryMinute()
```
Days limits the job to a specific set of weekdays.
```go
scheduler.NewJobBuilder(nil).
Days(time.Monday, time.Wednesday, time.Friday).
DailyAt("07:00")
```
Environments restricts job registration to specific environment names (e.g. "production", "staging").
```go
scheduler.NewJobBuilder(nil).Environments("production").Daily()
```
Fridays limits the job to Fridays.
```go
scheduler.NewJobBuilder(nil).Fridays().DailyAt("09:00")
```
Mondays limits the job to Mondays.
```go
scheduler.NewJobBuilder(nil).Mondays().DailyAt("09:00")
```
Saturdays limits the job to Saturdays.
```go
scheduler.NewJobBuilder(nil).Saturdays().DailyAt("09:00")
```
Skip prevents scheduling the job if the provided condition returns true.
```go
enabled := false
scheduler.NewJobBuilder(nil).
Skip(func() bool { return !enabled }).
Daily()
```
Sundays limits the job to Sundays.
```go
scheduler.NewJobBuilder(nil).Sundays().DailyAt("09:00")
```
Thursdays limits the job to Thursdays.
```go
scheduler.NewJobBuilder(nil).Thursdays().DailyAt("09:00")
```
Tuesdays limits the job to Tuesdays.
```go
scheduler.NewJobBuilder(nil).Tuesdays().DailyAt("09:00")
```
UnlessBetween prevents the job from running between the provided HH:MM times.
```go
scheduler.NewJobBuilder(nil).
UnlessBetween("22:00", "06:00").
EveryMinute()
```
Wednesdays limits the job to Wednesdays.
```go
scheduler.NewJobBuilder(nil).Wednesdays().DailyAt("09:00")
```
Weekdays limits the job to run only on weekdays (Mon-Fri).
```go
scheduler.NewJobBuilder(nil).Weekdays().DailyAt("09:00")
```
Weekends limits the job to run only on weekends (Sat-Sun).
```go
scheduler.NewJobBuilder(nil).Weekends().DailyAt("10:00")
```
When only schedules the job if the provided condition returns true.
```go
flag := true
scheduler.NewJobBuilder(nil).
When(func() bool { return flag }).
Daily()
```
## Hooks
After sets a hook to run after task execution.
```go
scheduler.NewJobBuilder(nil).
After(func() { println("after") }).
Daily()
```
Before sets a hook to run before task execution.
```go
scheduler.NewJobBuilder(nil).
Before(func() { println("before") }).
Daily()
```
OnFailure sets a hook to run after failed task execution.
```go
scheduler.NewJobBuilder(nil).
OnFailure(func() { println("failure") }).
Daily()
```
OnSuccess sets a hook to run after successful task execution.
```go
scheduler.NewJobBuilder(nil).
OnSuccess(func() { println("success") }).
Daily()
```
## Locking
NewRedisLocker creates a RedisLocker with a client and TTL.
```go
client := redis.NewClient(&redis.Options{}) // replace with your client
locker := scheduler.NewRedisLocker(client, time.Minute)
_, _ = locker.Lock(context.Background(), "job")
```
## Metadata
JobMetadata returns a copy of the tracked job metadata keyed by job ID.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
b := scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
for id, meta := range b.JobMetadata() {
_ = id
_ = meta.Name
}
```
Name sets an explicit job name.
```go
scheduler.NewJobBuilder(nil).
Name("cache:refresh").
HourlyAt(15)
```
## Scheduling
Cron sets the cron expression for the job.
```go
builder := scheduler.NewJobBuilder(nil).Cron("15 3 * * *")
fmt.Println(builder.CronExpr())
```
Daily schedules the job to run once per day at midnight.
```go
scheduler.NewJobBuilder(nil).Daily()
```
DailyAt schedules the job to run daily at a specific time (e.g., "13:00").
```go
scheduler.NewJobBuilder(nil).DailyAt("12:30")
```
DaysOfMonth schedules the job to run on specific days of the month at a given time.
```go
scheduler.NewJobBuilder(nil).DaysOfMonth([]int{5, 20}, "07:15")
```
Do schedules the job with the provided task function.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
Name("cleanup").
Cron("0 0 * * *").
Do(func() {})
```
Every schedules a job to run every X seconds, minutes, or hours.
```go
scheduler.NewJobBuilder(nil).
Every(10).
Minutes()
```
EveryFifteenMinutes schedules the job to run every 15 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryFifteenMinutes().Do(func() {})
```
EveryFifteenSeconds schedules the job to run every 15 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryFifteenSeconds().Do(func() {})
```
EveryFiveMinutes schedules the job to run every 5 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryFiveMinutes().Do(func() {})
```
EveryFiveSeconds schedules the job to run every 5 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryFiveSeconds().Do(func() {})
```
EveryFourHours schedules the job to run every four hours at the specified minute.
```go
scheduler.NewJobBuilder(nil).EveryFourHours(25)
```
EveryFourMinutes schedules the job to run every 4 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryFourMinutes().Do(func() {})
```
EveryMinute schedules the job to run every 1 minute.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryMinute().Do(func() {})
```
EveryOddHour schedules the job to run every odd-numbered hour at the specified minute.
```go
scheduler.NewJobBuilder(nil).EveryOddHour(10)
```
EverySecond schedules the job to run every 1 second.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
```
EverySixHours schedules the job to run every six hours at the specified minute.
```go
scheduler.NewJobBuilder(nil).EverySixHours(30)
```
EveryTenMinutes schedules the job to run every 10 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryTenMinutes().Do(func() {})
```
EveryTenSeconds schedules the job to run every 10 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryTenSeconds().Do(func() {})
```
EveryThirtyMinutes schedules the job to run every 30 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryThirtyMinutes().Do(func() {})
```
EveryThirtySeconds schedules the job to run every 30 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryThirtySeconds().Do(func() {})
```
EveryThreeHours schedules the job to run every three hours at the specified minute.
```go
scheduler.NewJobBuilder(nil).EveryThreeHours(20)
```
EveryThreeMinutes schedules the job to run every 3 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryThreeMinutes().Do(func() {})
```
EveryTwentySeconds schedules the job to run every 20 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryTwentySeconds().Do(func() {})
```
EveryTwoHours schedules the job to run every two hours at the specified minute.
```go
scheduler.NewJobBuilder(nil).EveryTwoHours(15)
```
EveryTwoMinutes schedules the job to run every 2 minutes.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryTwoMinutes().Do(func() {})
```
EveryTwoSeconds schedules the job to run every 2 seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EveryTwoSeconds().Do(func() {})
```
Hourly schedules the job to run every hour.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).Hourly().Do(func() {})
```
HourlyAt schedules the job to run every hour at the specified minute.
```go
scheduler.NewJobBuilder(nil).HourlyAt(5)
```
Hours schedules the job to run every X hours.
```go
scheduler.NewJobBuilder(nil).Every(6).Hours()
```
LastDayOfMonth schedules the job to run on the last day of each month at a specific time.
```go
scheduler.NewJobBuilder(nil).LastDayOfMonth("23:30")
```
Minutes schedules the job to run every X minutes.
```go
scheduler.NewJobBuilder(nil).Every(15).Minutes()
```
Monthly schedules the job to run on the first day of each month at midnight.
```go
scheduler.NewJobBuilder(nil).Monthly()
```
MonthlyOn schedules the job to run on a specific day of the month at a given time.
```go
scheduler.NewJobBuilder(nil).MonthlyOn(15, "09:30")
```
Quarterly schedules the job to run on the first day of each quarter at midnight.
```go
scheduler.NewJobBuilder(nil).Quarterly()
```
QuarterlyOn schedules the job to run on a specific day of each quarter at a given time.
```go
scheduler.NewJobBuilder(nil).QuarterlyOn(3, "12:00")
```
Seconds schedules the job to run every X seconds.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).
Every(3).
Seconds().
Do(func() {})
```
TwiceDaily schedules the job to run daily at two specified hours (e.g., 1 and 13).
```go
scheduler.NewJobBuilder(nil).TwiceDaily(1, 13)
```
TwiceDailyAt schedules the job to run daily at two specified times (e.g., 1:15 and 13:15).
```go
scheduler.NewJobBuilder(nil).TwiceDailyAt(1, 13, 15)
```
TwiceMonthly schedules the job to run on two specific days of the month at the given time.
```go
scheduler.NewJobBuilder(nil).TwiceMonthly(1, 15, "10:00")
```
Weekly schedules the job to run once per week on Sunday at midnight.
```go
scheduler.NewJobBuilder(nil).Weekly()
```
WeeklyOn schedules the job to run weekly on a specific day of the week and time.
Day uses 0 = Sunday through 6 = Saturday.
```go
scheduler.NewJobBuilder(nil).WeeklyOn(1, "8:00")
```
Yearly schedules the job to run on January 1st every year at midnight.
```go
scheduler.NewJobBuilder(nil).Yearly()
```
YearlyOn schedules the job to run every year on a specific month, day, and time.
```go
scheduler.NewJobBuilder(nil).YearlyOn(12, 25, "06:45")
```
## State management
RetainState allows the job to retain its state after execution.
```go
s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
builder := scheduler.NewJobBuilder(s).EverySecond().RetainState()
builder.Do(func() {})
builder.Do(func() {})
```