Migrating from Cron Jobs to Schedules
Cron Jobs are a legacy feature and will be migrated to Schedules in the future. You can migrate now with a configuration change. Your route code keeps working as it is.
After you migrate, you can:
- Set a timezone for each schedule instead of using UTC
- Enable, disable, or run each schedule without redeploying
- Run schedules in Preview branches and custom environments
- See the execution history of each schedule
- Switch to a private function or a queue topic later
A schedule with a path target calls your route the same way a Cron Job does:
- The
user-agentheader isvercel-cron/1.0. - The
x-vercel-cron-scheduleheader contains the cron expression. - If you set
CRON_SECRET, theAuthorizationheader containsBearer <CRON_SECRET>, so yourCRON_SECRETcheck keeps working.
Path schedules send a POST request by default, but Cron Jobs send GET. Set requestMethod to GET when you migrate so your route handler doesn't need to change.
Schedule requests also include schedule headers, such as the execution ID and scheduled time.
Cron Jobs live in the
cronsproperty ofvercel.json:vercel.json{ "$schema": "https://openapi.vercel.sh/vercel.json", "crons": [ { "path": "/api/daily-report", "schedule": "0 9 * * *" }, { "path": "/api/cleanup", "schedule": "0 * * * *" } ] }You can also see them on the Cron Jobs tab of your project's Schedules page in the dashboard.
For each Cron Job, add an entry to
schedules. Use a name as the key, keep the samepathandschedule, and add"requestMethod": "GET":vercel.json{ "$schema": "https://openapi.vercel.sh/vercel.json", "schedules": { "daily-report": { "schedule": "0 9 * * *", "path": "/api/daily-report", "requestMethod": "GET" }, "cleanup": { "schedule": "0 * * * *", "path": "/api/cleanup", "requestMethod": "GET" } } }Remove the
cronsentries in the same change. If you keep both, your route runs twice for each occurrence.Deploy your project:
terminalvercel deploy --prodEdgenivo creates the schedules when the deployment becomes your production deployment, and stops running the Cron Jobs you removed.
- Open your project in the Edgenivo dashboard and select Schedules.
- Check that your schedules appear in the list.
- Select a schedule and click Run Schedule to test it without waiting for its next run.
You can also run a schedule from the CLI:
terminalvercel schedules invoke daily-report
Schedules start within a jitter window instead of at an exact time. The limits also differ by plan:
| Plan | Cron Jobs | Schedules |
|---|---|---|
| Hobby | At most once per day, at any time within the scheduled hour | At most once every 15 minutes, within a 15-minute jitter window |
| Pro and Enterprise | At the scheduled minute | Within the jitter window, which defaults to 1 minute and can be set from 1 to 15 minutes |
On Pro and Enterprise, add jitter to change the window:
{
"schedules": {
"cleanup": {
"schedule": "0 * * * *",
"path": "/api/cleanup",
"requestMethod": "GET",
"jitter": 5
}
}
}Disable Cron Jobs on the Cron Jobs tab of the Schedules page only pauses Cron Jobs. It doesn't affect schedules. To pause a schedule, select it and choose Disable from the menu next to Run Schedule, or run:
vercel schedules disable cleanupDisabling a static schedule doesn't require a redeploy. See Managing schedules.
A Cron Job path is a public URL, so anyone can call it unless you check CRON_SECRET. After you migrate, you can switch to a function target. Function targets have no public URL, and only Edgenivo's scheduler can invoke them.
- Install
@vercel/schedulesand wrap your handler inhandleSchedule. - Add a
schedule/v1betatrigger to the function. - Replace
pathandrequestMethodwithfunction.
See Function targets for the full steps. Keep the same schedule name so Edgenivo updates the existing schedule instead of creating a new one.
- Concepts: Learn how environments, late runs, and retries work.
- Handling scheduled runs: Make your handler safe to run more than once.
vercel schedules: Manage schedules from the terminal.
Was this helpful?