macOS runs background automation through three overlapping mechanisms: Login Items, LaunchAgents, and LaunchDaemons. Understanding how each one works and when to use it is essential for IT administrators managing Mac fleets, developers shipping background services, and users auditing what runs at startup. This guide covers the differences, shows example plist files, and walks through modern launchctl syntax.
The Three Background Item Types
Login Items
Login Items are applications or scripts that launch automatically when a user logs in. They are managed through System Settings > General > Login Items & Extensions (macOS 13+) or System Preferences > Users & Groups > Login Items on older releases. Login Items always run in the user's GUI session and appear in the Dock or menu bar like any other app. Common examples include Slack, 1Password, and Dropbox.
You can list Login Items from the command line with:
osascript -e 'tell application "System Events" to get the name of every login item'
Add one programmatically with:
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/Slack.app", hidden:false}'
On macOS 13 and later, apps that ship a helper bundle (SMAppService) are surfaced in the same Login Items pane alongside LaunchAgents and LaunchDaemons.
LaunchAgents
LaunchAgents are property-list files that tell launchd to run a program in the context of a logged-in user. They live in one of three directories depending on scope, and they can access the GUI, keychain, and user environment.
LaunchDaemons
LaunchDaemons are plist files that tell launchd to run a program as a system-level service. They run as root by default (or another user specified in the plist), start before any user logs in, and cannot display a UI. Use them for services that must run regardless of who is logged in — monitoring agents, VPN clients, printer daemons.
Comparison Table
| Type | Location | Runs As | Runs When | Common Use Cases |
|---|---|---|---|---|
| Login Item | System Settings > Login Items, or ~/Library/Application Support | Logged-in user | User logs in | GUI apps like Slack, 1Password |
| User LaunchAgent | ~/Library/LaunchAgents | Current user | User logs in | Per-user scripts, dev tooling, syncs |
| Global LaunchAgent | /Library/LaunchAgents | Any user | Any user logs in | Org-wide user tooling, MDM user agents |
| System LaunchAgent | /System/Library/LaunchAgents | Any user | Any user logs in | Apple-shipped user services (do not edit) |
| LaunchDaemon | /Library/LaunchDaemons | root (default) | Boot, before login | Monitoring, VPN, printer, backup daemons |
| System LaunchDaemon | /System/Library/LaunchDaemons | root | Boot | Apple-shipped system services (do not edit) |
Never modify anything under /System/Library — those paths are protected by System Integrity Protection (SIP) and are managed exclusively by macOS updates.
Example LaunchAgent: Run a Script Every 5 Minutes
Save the following file as ~/Library/LaunchAgents/com.example.cleanup.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.cleanup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/you/bin/cleanup.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>StandardOutPath</key>
<string>/tmp/com.example.cleanup.out</string>
<key>StandardErrorPath</key>
<string>/tmp/com.example.cleanup.err</string>
</dict>
</plist>
StartInterval fires the job every 300 seconds. For a one-shot job that only runs at login, replace StartInterval with:
<key>RunAtLoad</key>
<true/>
Validate the XML before loading it:
plutil -lint ~/Library/LaunchAgents/com.example.cleanup.plist
Loading, Unloading, and Inspecting with launchctl
Apple replaced the legacy launchctl load / launchctl unload commands with domain-aware equivalents. Use these on macOS 11 and later.
Bootstrap (load) a user LaunchAgent:
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.example.cleanup.plist
Bootstrap a LaunchDaemon:
sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.daemon.plist
Bootout (unload) a service:
launchctl bootout gui/$UID/com.example.cleanup
List loaded services and their last exit code:
launchctl list | grep com.example
Force an immediate restart (useful after editing the plist):
sudo launchctl kickstart -k system/com.example.daemon
The -k flag kills the running instance first. Drop sudo and use gui/$UID/com.example.cleanup for user agents.
Troubleshooting
Permission Denied When Loading a Plist
- LaunchDaemons must be owned by
root:wheelwith mode644. Fix withsudo chown root:wheel /Library/LaunchDaemons/com.example.daemon.plist && sudo chmod 644 /Library/LaunchDaemons/com.example.daemon.plist. - LaunchAgents must be owned by your user and not world-writable.
- If the program the plist runs lives in a protected location like
~/Documentsor~/Desktop, macOS TCC (Transparency, Consent, and Control) may block execution silently. Move the script to~/binor grant Full Disk Access to/bin/bash.
"Service cannot be loaded" or "Load failed: 5"
- Run
plutil -lintto catch XML errors - Confirm
Labelmatches the filename (minus.plist) - Check that the executable in
ProgramArgumentsexists and has the execute bit set - Look at
Console.appfiltered bylaunchdfor the exact reason
Debugging With Console.app and log stream
Open Console.app and filter by process launchd or by your service's Label. For a live terminal view, run:
log stream --predicate 'subsystem == "com.apple.xpc.launchd"' --info
This streams every launchd event in real time and is the fastest way to see why a service failed to start, exited, or was throttled (launchd refuses to relaunch a service more than once every 10 seconds by default).
Background Items in macOS 13 and Later
Since macOS 13 Ventura, every third-party plist and SMAppService helper appears in System Settings > General > Login Items & Extensions. Users can toggle items off from that pane, which disables the underlying LaunchAgent or LaunchDaemon without deleting the plist. When an installer adds a new background item, macOS shows a "Background item added" notification in Notification Center. If you are distributing a pkg installer or helper tool, expect users to see that notification and plan your onboarding around it.
To audit background items from the command line, run:
sfltool dumpbtm
This dumps the Background Task Management database and lists every registered Login Item, agent, and daemon with its enabled state.