Skip to main content
macOSintermediate

How to Manage Login Items, LaunchAgents, and LaunchDaemons on macOS

Learn how Login Items, LaunchAgents, and LaunchDaemons work on macOS, how to create plist files, and how to manage background services with launchctl.

8 min readUpdated April 2026

Want us to handle this for you?

Get expert help →

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

TypeLocationRuns AsRuns WhenCommon Use Cases
Login ItemSystem Settings > Login Items, or ~/Library/Application SupportLogged-in userUser logs inGUI apps like Slack, 1Password
User LaunchAgent~/Library/LaunchAgentsCurrent userUser logs inPer-user scripts, dev tooling, syncs
Global LaunchAgent/Library/LaunchAgentsAny userAny user logs inOrg-wide user tooling, MDM user agents
System LaunchAgent/System/Library/LaunchAgentsAny userAny user logs inApple-shipped user services (do not edit)
LaunchDaemon/Library/LaunchDaemonsroot (default)Boot, before loginMonitoring, VPN, printer, backup daemons
System LaunchDaemon/System/Library/LaunchDaemonsrootBootApple-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:wheel with mode 644. Fix with sudo 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 ~/Documents or ~/Desktop, macOS TCC (Transparency, Consent, and Control) may block execution silently. Move the script to ~/bin or grant Full Disk Access to /bin/bash.

"Service cannot be loaded" or "Load failed: 5"

  • Run plutil -lint to catch XML errors
  • Confirm Label matches the filename (minus .plist)
  • Check that the executable in ProgramArguments exists and has the execute bit set
  • Look at Console.app filtered by launchd for 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.


Frequently Asked Questions

Find answers to common questions

LaunchAgents run in the context of a logged-in user and have access to the user's GUI session, keychain, and home directory. LaunchDaemons run as root (or another system user) before any user logs in and have no access to the GUI. Use a LaunchAgent for per-user automation like syncing a Dropbox folder, and a LaunchDaemon for system-wide services like a monitoring agent or VPN daemon.

Starting with macOS 13 Ventura, the system surfaces every LaunchAgent, LaunchDaemon, and Login Item installed by a third-party app in System Settings > General > Login Items & Extensions. When an installer drops a new plist into a LaunchAgents or LaunchDaemons directory, macOS shows a "Background item added" notification so users can audit what is running in the background. You can toggle items off from that pane.

The load and unload subcommands still function on modern macOS but are deprecated and may produce warnings or behave inconsistently on Apple silicon. Apple recommends using launchctl bootstrap and launchctl bootout with an explicit domain target such as gui/$UID or system. The newer commands surface clearer error messages and integrate with the service management framework in macOS 13 and later.

Put it in ~/Library/LaunchAgents. That directory is owned by your user account, does not require sudo, and is loaded automatically when you log in. Name the file with reverse-DNS notation such as com.yourname.backupscript.plist and set the Label key inside the plist to match the filename without the .plist extension.

This error almost always means the plist is malformed, the Label does not match the filename, or the file has incorrect ownership or permissions. Validate the XML with plutil -lint /path/to/file.plist, confirm the file is owned by root:wheel for LaunchDaemons (or your user for LaunchAgents), and ensure permissions are 644. Check Console.app for the exact reason the service refused to load.

Pipe the agent's stdout and stderr to log files using the StandardOutPath and StandardErrorPath keys in the plist, then tail those files. For system-level logging, open Console.app or run log stream --predicate 'subsystem == "com.apple.xpc.launchd"' in Terminal to watch launchd events in real time. Use launchctl list to see the last exit code and PID of every loaded service.

Need Professional IT & Security Help?

Our team of experts is ready to help protect and optimize your technology infrastructure.