TechEarl

How to Set the Desktop Wallpaper From the macOS Command Line

Set the macOS desktop wallpaper from the terminal with one osascript line that covers every display at once, plus the modern caching gotcha and how to script a random wallpaper.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Set the macOS desktop wallpaper from the command line with osascript across every display, work around the wallpaper cache, and script a random background.

The one command that sets your Mac's desktop wallpaper from the terminal, across every monitor at once, is this:

bash
osascript -e 'tell application "System Events" to set picture of every desktop to "/Users/you/Pictures/bg.jpg"'

That is the whole job for most people. It runs as your own user, takes effect immediately, and does not need sudo (the wallpaper is a per-user preference, not a system one). The rest of this page is the detail behind it: the single-display form, the path rules, the caching bug that bites on modern macOS, the Automation permission prompt, and a script that rotates a random wallpaper from a folder.

Set the wallpaper on every display

System Events is the scriptable app that owns the desktops, and every desktop is the part that matters. In the AppleScript object model there is one desktop per attached display, so every desktop hits all of them in one shot:

bash
# Every attached display:
osascript -e 'tell application "System Events" to set picture of every desktop to "/Users/you/Pictures/bg.jpg"'

A note on Spaces: AppleScript does not expose a separate desktop object per Space, so every desktop enumerates displays, not Spaces. In practice the new wallpaper shows up on every Space too, because macOS keeps the same picture across a display's Spaces by default. If you have deliberately set a different wallpaper per Space, switch to the Space you want before running the command, since the change lands on the active Space's stored picture for that display.

Use an absolute path. AppleScript does not expand the shell ~, so ~/Pictures/bg.jpg passed as a literal string fails silently. If you want to use ~, let the shell expand it before AppleScript ever sees it:

bash
# Let the shell expand the home path, then hand the result to osascript:
osascript -e "tell application \"System Events\" to set picture of every desktop to \"$HOME/Pictures/bg.jpg\""

JPEG, PNG, and HEIC all work. The file has to exist and be readable, or you get an error (or, on recent macOS, nothing at all, which is the trap covered below).

Set the wallpaper on every desktop with AppleScript

If you write AppleScript proper (a .scpt you keep in Script Editor, or a step in a larger script) rather than a one-liner, the canonical form is the same set picture of every desktop statement spelled out as a tell block:

applescript
tell application "System Events"
    set picture of every desktop to "/Users/you/Pictures/bg.jpg"
end tell

every desktop is what makes this hit all displays and Spaces at once, instead of just the screen you happen to be looking at. To run that block straight from the shell without a saved file, flatten it into a single osascript -e invocation:

bash
osascript -e 'tell application "System Events" to set picture of every desktop to "/Users/you/Pictures/bg.jpg"'

Two caveats worth knowing. A Space created after you run this can keep the old picture until you recreate it, because macOS stamps the wallpaper onto a Space when it is built. And recent macOS versions are finicky about wallpaper scripting permissions: System Events needs Automation permission for the app driving it (see the permission section below), or the command runs and changes nothing.

The single-display Finder form

The older form many guides still show talks to Finder instead of System Events:

bash
# Primary display only:
osascript -e 'tell application "Finder" to set desktop picture to POSIX file "/Users/you/Pictures/bg.jpg"'

This works, but it only sets the main display. On a multi-monitor setup the second screen keeps its old wallpaper. That is the entire reason to prefer the System Events / every desktop form: one command, every screen. Reach for the Finder form only when you specifically want the primary display left to itself, which is rare.

The caching gotcha on modern macOS

Here is the one that wastes an afternoon. On Sonoma (14) and later, macOS reworked how it stores and caches the desktop picture: the state now lives in a per-user store at ~/Library/Application Support/com.apple.wallpaper/Store/Index.plist (a base64-encoded binary plist, the same encoding you reach for when you base64-encode or decode a file from the command line), which is why older scripts that poked the legacy desktoppicture.db SQLite file no longer have any effect. The practical symptom: if you set the wallpaper to a path, then overwrite the file at that same path with a new image and run the command again, the screen does not change. macOS sees the same path it already cached and skips the redraw.

Two reliable workarounds:

bash
# Option 1: give the new image a new path (a different filename defeats the cache):
cp new-art.jpg "/Users/you/Pictures/bg-$(date +%s).jpg"
osascript -e "tell application \"System Events\" to set picture of every desktop to \"/Users/you/Pictures/bg-$(date +%s).jpg\""

# Option 2: force a refresh after writing to the same path:
killall Dock
# On Sonoma and later, the wallpaper process is its own daemon:
killall WallpaperAgent 2>/dev/null

killall Dock is the long-standing nudge and is harmless: the Dock relaunches itself in a second. WallpaperAgent is the newer daemon that owns wallpaper rendering on Sonoma+; killing it forces a re-read, and it also relaunches on its own. The 2>/dev/null keeps it quiet on older macOS where that process does not exist. For automation, the cleanest answer is Option 1: write each new wallpaper to a unique filename and the cache problem never arises.

The Automation permission prompt

The first time a given terminal app drives System Events, macOS shows an Automation consent dialog: "Terminal wants to control System Events." This is the TCC (Transparency, Consent, and Control) gate that has applied to Apple Events since Mojave (10.14). Click OK and the grant is remembered per app under System Settings → Privacy & Security → Automation.

Two things to know:

  • A non-interactive script (a launchd job, an MDM-pushed script) cannot click that dialog. It either needs the controlling app pre-approved in the Automation list, or it fails. This is the most common reason a wallpaper script that works in your interactive terminal does nothing when run from a launch agent.
  • If you ever denied the prompt by mistake, the toggle to re-enable it lives in that same Automation pane.

Script a random wallpaper from a folder

This is the payoff: point a script at a folder of images and have it pick one at random. It writes to a unique temp path each run so the cache gotcha above never triggers.

bash
#!/usr/bin/env bash
# te-random-wallpaper.sh: set a random desktop wallpaper from a folder.
# Author: Ishan Karunaratne — https://techearl.com/set-mac-wallpaper-command-line
set -euo pipefail

te_random_wallpaper() {
  local dir="${1:-$HOME/Pictures/Wallpapers}"
  # Pick one image at random:
  local pick
  pick="$(find "$dir" -type f \( -iname '*.jpg' -o -iname '*.png' -o -iname '*.heic' \) | sort -R | head -n 1)"
  if [ -z "$pick" ]; then
    echo "No images found in $dir" >&2
    return 1
  fi
  osascript -e "tell application \"System Events\" to set picture of every desktop to \"$pick\""
  echo "Wallpaper set to: $pick"
}

te_random_wallpaper "$@"

Save it, make it executable with chmod +x te-random-wallpaper.sh, and run it. To rotate the wallpaper on a schedule, wire it into a launchd agent (or cron) and remember the Automation-permission caveat above: the controlling process has to be pre-approved or the osascript call silently does nothing.

If you live in the terminal for this kind of thing, the same osascript pattern drives a lot of macOS appearance from the command line. See the related guides below for toggling dark mode and changing the system volume the same way.

Two usual causes. Either the path is wrong (AppleScript does not expand ~, so use an absolute path or let the shell expand $HOME first), or you overwrote the image at a path macOS has already cached. On Sonoma and later, write the new image to a fresh filename, or run killall Dock and killall WallpaperAgent to force a redraw.

No. The desktop picture is a per-user preference, so it runs as your own user without sudo. Adding sudo would run the command as root, which sets root's wallpaper, not yours.

Because of the Automation permission prompt. macOS requires the controlling app to be granted permission to send Apple Events to System Events. An interactive terminal can click the consent dialog the first time; a background job cannot, so the controlling process must be pre-approved under System Settings, Privacy and Security, Automation, or the call does nothing.

The every desktop form sets the same image on all displays at once (one desktop object per display). For a different image per display you have to address each desktop by index in an AppleScript loop, which is fiddly across macOS versions. For per-display control with less pain, a dedicated tool such as desktoppr is more reliable than scripting it by hand.

See also

Sources

Authoritative references this article was fact-checked against.

TagsmacOSwallpaperosascriptAppleScriptCLIdesktop backgroundSystem Events

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts