Two ways:
System.schedule(jobName, cronExpression, instance)— Apex code, deterministic, deploys with the org.- Setup → Apex Classes → Schedule Apex — point-and-click UI, fine for one-off setups.
1. Apex — System.schedule
System.schedule(
'Nightly Stale Case Cleanup', // friendly name
'0 0 2 * * ?', // cron: 2 AM every day
new NightlyCleanupSchedulable() // your Schedulable instance
);
System.schedule returns the CronTrigger.Id. Stash it if you want to programmatically abort later.
The class must implement Schedulable:
public class NightlyCleanupSchedulable implements Schedulable {
public void execute(SchedulableContext ctx) {
Database.executeBatch(new StaleCaseCloserBatch(), 200);
}
}
2. Setup UI
- Setup → Apex Classes.
- Click Schedule Apex (top-right).
- Fill in:
- Job Name
- Apex Class (picklist — only
Schedulableclasses show) - Frequency (weekly / monthly / specific days)
- Start / End Date, Preferred Start Time
- Click Save.
Salesforce builds the cron expression from your selections. It’s limited — you can express daily/weekly/monthly but not arbitrary cron like “every 5 minutes.” For complex schedules, use System.schedule from Apex.
Cron expression quick reference
Format: Seconds Minutes Hours Day-of-Month Month Day-of-Week [Year]
| Cron | Meaning |
|---|---|
0 0 2 * * ? | 2:00 AM every day |
0 0 * * * ? | Every hour on the hour |
0 */15 * * * ? | Every 15 minutes |
0 0 8 ? * MON | Mondays at 8 AM |
0 30 6 1 * ? | 6:30 AM on the 1st of every month |
0 0 0 ? * SUN | Midnight every Sunday |
The ? placeholder means “no specific value” — you must use it in either day-of-month or day-of-week, never both.
Pre-built helpers
For simple “every N minutes” schedules, Apex provides convenience methods:
// Run MyBatch once in 30 minutes
System.scheduleBatch(new MyBatch(), 'MyBatch Once', 30, 200);
scheduleBatch builds the cron for you and creates a Schedulable wrapper automatically.
Schedule once per Apex class? Or many times?
You can schedule the same class under multiple names with different cron expressions:
System.schedule('Quarterly Report Q1', '0 0 6 1 1 ?', new QuarterReport());
System.schedule('Quarterly Report Q2', '0 0 6 1 4 ?', new QuarterReport());
System.schedule('Quarterly Report Q3', '0 0 6 1 7 ?', new QuarterReport());
System.schedule('Quarterly Report Q4', '0 0 6 1 10 ?', new QuarterReport());
Four separate CronTrigger rows, all pointing to the same class. Each counts against the 100-scheduled-job limit.
Limits to know
| Limit | Value |
|---|---|
| Active scheduled jobs (Schedulable) in the org | 100 |
System.schedule calls per transaction | 250 |
| Per-job cron resolution | 1 minute (seconds field accepted but ignored in practice) |
Common interview follow-ups
- Where do I find the JobId after scheduling? —
System.schedulereturns it. You can also queryCronTriggerby name. - Can I update the schedule? — Not directly. Abort the existing CronTrigger and call
System.scheduleagain with the new expression. - Does deploying overwrite the schedule? — No. The schedule is data (a
CronTrigger), not metadata.
Verified against: Apex Developer Guide — Scheduled Apex. Last reviewed 2026-05-17.