Schedule Tasks on Mac With launchd (and cron)
You have a script — a backup, a cleanup, a report — and you want your Mac to run it every morning without you. The Mac-native answer is launchd: you describe the job in a small plist file, load it once, and macOS takes it from there. Crucially, unlike cron, launchd notices when your Mac was asleep at the scheduled time and runs the job when it wakes.
Anatomy of a LaunchAgent
Per-user scheduled jobs are LaunchAgents: plist files in ~/Library/LaunchAgents/. Here's a complete one that runs a script every day at 9:00 — save it as ~/Library/LaunchAgents/com.example.dailyreport.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.dailyreport</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>/Users/you/scripts/report.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/dailyreport.log</string>
<key>StandardErrorPath</key>
<string>/tmp/dailyreport.err</string>
</dict>
</plist>
The three keys that matter: Label is the job's unique name (reverse-DNS style, matching the filename by convention); ProgramArguments is the command split into an array, one element per word; StartCalendarInterval is the schedule — any combination of Minute, Hour, Day, Weekday, Month. Omitted keys mean "every" — so Minute 0 alone runs hourly. For "every N seconds" jobs, swap it for StartInterval with an integer. The two log paths aren't required, but future-you will want them when a job silently misbehaves.
Load, test, and unload
# load the job (starts scheduling it)
launchctl load ~/Library/LaunchAgents/com.example.dailyreport.plist
# run it right now to test, by label
launchctl start com.example.dailyreport
# undo: unload the job (stops scheduling)
launchctl unload ~/Library/LaunchAgents/com.example.dailyreport.plist
To remove the job permanently, unload it and delete the plist. launchctl load/unload are technically the legacy spellings — the modern equivalents are launchctl bootstrap gui/$(id -u) path and launchctl bootout gui/$(id -u)/com.example.dailyreport — but the legacy forms still work on Ventura through Sequoia and are easier to remember. Check a job is loaded with launchctl list | grep example, and check your log files after the first scheduled run.
One permission note: scripts touching Documents, Desktop, or external drives may need Full Disk Access granted to the tools involved, or the job will fail with permission errors that only appear in your StandardErrorPath file.
What about cron?
cron still ships with macOS and still works: crontab -e opens your table, and a line like 0 9 * * * /Users/you/scripts/report.sh runs daily at 9:00 (undo by removing the line, or crontab -r to clear the table). For a quick personal job it's genuinely less ceremony.
The deal-breaker on a laptop: cron skips any run that falls while the Mac is asleep — a 9:00 job simply doesn't happen if the lid was closed, and cron doesn't care. launchd's StartCalendarInterval queues the missed run and fires it once on wake. If the Mac is awake around your scheduled times anyway (a desktop, a server), cron is fine; for anything that must actually happen daily on a laptop, use launchd.
Three refinements once the basics work. RunAtLoad (a boolean key) fires the job immediately whenever it loads — including at login, which is how "run this when I log in" is done. WatchPaths (an array of paths) triggers the job when a file or folder changes, turning a script into a folder action. And before loading anything, lint it: plutil -lint ~/Library/LaunchAgents/com.example.dailyreport.plist catches malformed XML in a second — a mistyped tag otherwise fails silently, which in launchd debugging feels like a curse.
launchd handles your scripts. Mainspring handles your settings — 90+ hidden macOS tweaks as labelled toggles, every one reversible, no plists required.
Try Mainspring free →Signed & notarized by Apple · 1-day free trial · $29 once
What to schedule first
A natural first job: a nightly tmutil startbackup before bed, or a Shortcuts automation via the shortcuts command — see our guide to running Shortcuts from Terminal for that pairing.