Sleep until the next cron expression occurrence in Vercel Workflows
npm install workflow-cron-sleepSleep until the next cron expression occurrence in Workflow DevKit.
cronSleep pauses your workflow until the next single occurrence of a cron expression. It is not a recurring cron job scheduler.
``typescript`
// This sleeps ONCE until the next 9 AM, then continues execution
await cronSleep("0 9 *", { timezone: "America/New_York" })
If you need recurring behavior, you'll need to structure your workflows to handle that (see Recurring Workflows below).
`bash`
npm install workflow-cron-sleep
`typescript
import { cronSleep } from "workflow-cron-sleep"
async function myWorkflow() {
"use workflow"
// Sleep until the next 9 AM Eastern Time
await cronSleep("0 9 *", { timezone: "America/New_York" })
// This runs once at 9 AM, then the workflow ends
console.log("It's 9 AM!")
}
`
Important: When no timezone is specified, cronSleep uses the local system timezone of the server running your workflow. On Vercel, this depends on the region your function is deployed to.
For predictable behavior, always specify a timezone:
`typescript
// Recommended: Explicit timezone
await cronSleep("0 9 *", { timezone: "America/New_York" })
// UTC for timezone-agnostic scheduling
await cronSleep("0 14 *", { timezone: "UTC" })
// Not recommended: Server's local timezone (varies by region)
await cronSleep("0 9 *")
`
Sleeps until the next occurrence of a cron expression.
#### Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| cronExpression | string | A valid cron expression (e.g., "0 9 *") |options.timezone
| | string (optional) | IANA timezone (e.g., "America/New_York", "UTC") |
#### Returns
Promise - Resolves when the workflow resumes at the scheduled time.
#### Throws
Throws an error if the cron expression is invalid or has no future occurrences.
| Expression | Description |
|------------|-------------|
| 0 9 * | Every day at 9:00 AM |30 8 1
| | Every Monday at 8:30 AM |0 0 1
| | First day of every month at midnight |0 /2
| | Every 2 hours |0 9 1-5
| | Weekdays at 9:00 AM |59 23 L
| | Last day of every month at 11:59 PM |
Since cronSleep only sleeps until the next occurrence, you need to design your workflow architecture to handle recurring schedules.
The simplest approach is a single workflow with a while (true) loop that sleeps and triggers your business logic workflow repeatedly:
workflows/report-scheduler.ts - The scheduler workflow
`typescript
import { cronSleep } from "workflow-cron-sleep"
import { triggerReportProcessor } from "./steps"
export interface ReportSchedulerInput {
reportId: string
cronExpression: string
}
export async function reportScheduler(input: ReportSchedulerInput) {
"use workflow"
const { reportId, cronExpression } = input
while (true) {
// Wait for the next scheduled time
await cronSleep(cronExpression, { timezone: "America/New_York" })
// Trigger the processor workflow
await triggerReportProcessor(reportId)
}
}
`
workflows/steps.ts - Step function to start the processor
`typescript
import { start } from "workflow/api"
import { reportProcessorWorkflow } from "./report-processor"
export async function triggerReportProcessor(reportId: string) {
"use step"
await start(reportProcessorWorkflow, [{ reportId }])
}
`
workflows/report-processor.ts - The processor workflow (business logic)
`typescript
export interface ReportProcessorInput {
reportId: string
}
export async function reportProcessorWorkflow(input: ReportProcessorInput) {
"use workflow"
const { reportId } = input
// Your business logic here - completely agnostic to scheduling
await generateReport(reportId)
await sendNotifications(reportId)
}
`
`typescript
import { start, getRun } from "workflow/api"
import { reportScheduler } from "./workflows/report-scheduler"
// Start the cron scheduler
const run = await start(reportScheduler, [{
reportId: "daily-report",
cronExpression: "0 9 *"
}])
// Save the run ID to cancel later
await db.cronJobs.create({ runId: run.id })
// To cancel the cron (stops the while loop)
const currentRun = getRun(run.id)
await currentRun.cancel()
`
This pattern keeps your scheduling and business logic decoupled. The scheduler runs forever, triggering your processor workflow on schedule. Save the runId so you can cancel if the cron expression changes or the job is no longer needed.
- workflow` package (Vercel Workflows SDK)
MIT