# Add Apps to the Dock

> Add Apps to the Dock of your macOS Devices once with an Applivery Script, keeping each user's existing Dock and leaving it fully editable.

Source: https://docs.applivery.com/en/device-management/apple/macos/scripts/add-dock-apps/  •  Last updated: 2026-11-12

**Key topics:** macOS Dock, Applivery Scripts, Script execution methods, Troubleshooting, Applivery, macOS, Dock

---

**TL;DR:** Use an Applivery Script in Loop mode to add Apps to each user's macOS Dock once, without replacing or locking it.

A new Mac shows Apple's default Dock, and the Apps your organization deploys aren't in it. With this Script, you can add the Apps you choose to the end of each user's Dock, once. It doesn't replace what's already there and it doesn't lock anything: users can still reorder, remove, or add items afterwards.

Since it's a Bash **Script**, the Dock isn't managed by the Policy. The Script adds each App one time per user and then leaves the Dock alone.

:::info
If you need the Policy to define the Dock, pin Apps so they can't be removed, or lock the Dock, use the **Dock Configuration** of the Policy instead. You can learn more about it by following this [link.](https://docs.applivery.com/en/device-management/apple/macos/policies/dock-configuration/)
:::

## Requirements

-   macOS Devices enrolled in Applivery and assigned to a **Policy**.
    
-   The **Applivery MDM Agent** enabled in that Policy. Scripts are run by the Agent. Learn more about the [macOS Agent](https://docs.applivery.com/en/device-management/apple/apple-policies/agent/#agent-app-for-macos-devices).
    
-   The Apps you want in the Dock deployed to the Devices. The Script doesn't install Apps, it only adds them to the Dock.
    
-   The `add-dock-apps.sh` Script, included at the [end of this guide](#script).
    

## How the Script works

Each time it runs, the Script does the following:

1.  Finds the user logged in at the console of the Device. If there's none (for example, at the login window), it exits and tries again on the next run.
    
2.  Goes through the Apps in the list and decides which ones are still pending for that user:
    
    -   An App that was already handled for that user on a previous run is skipped. This is what keeps the Dock editable: if the user removed the App, it stays removed.
        
    -   An App that isn't installed on the Device yet is skipped, and checked again on the next run.
        
3.  If no App is pending, it exits without touching the Dock.
    
4.  Waits until that user's Dock is running and ready, for up to five minutes. If it isn't ready by then, it exits and tries again on the next run, so the Dock macOS builds on a first login is never replaced.
    
5.  For each pending App:
    
    -   If the user already has it in the Dock, nothing changes.
        
    -   Otherwise, it's added at the end of the Apps section of the Dock.
        
6.  Records each App it has handled for that user in `~/Library/Application Support/AppliveryDockSetup`, so it isn't handled again.
    
7.  Restarts the Dock only if something was added, so the new Apps show up. The Dock blinks for a moment when this happens.
    

:::info
The Script only uses tools included in macOS. You don't need to install anything else on the Device.
:::

## Choose the Apps

Open `add-dock-apps.sh` in a text editor and edit the `APPS` list with the full path of each App, one per line and between double quotes. This example adds four Apps:

```bash
APPS=(
    "/Applications/Google Chrome.app"
    "/Applications/Microsoft Teams.app"
    "/Applications/Slack.app"
    "/Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app"
)
```

:::info
Apps are added in the order of the list. To get the exact path of an App, select it in Finder, hold the **Option** key, and choose **Edit > Copy as Pathname** (or press **Option + Command + C**). Then paste it between the quotes.
:::

## Upload the Script to Applivery

Once in the [**Applivery Dashboard**](https://dashboard.applivery.io), go to the **Resources** section 1. Select the **Scripts** tab 2 and click the **\+ Create Script** 3 button.

![scripts](https://docs.applivery.com/int/_r2/media/09ac0a4e-3ad8-478f-9f15-3474973eec71/fd376c0b-bce9-4d08-ae11-215d7ce7f070.png)

Fill in the form as follows:

-   **Name**: `Add-DockApps-UserEditable` (or any name you prefer).
    
-   **Description**: optional, for example _Adds Apps to the user's Dock once and leaves it editable_.
    
-   **Language**: **Bash**.
    

Then click **Load from file** and select your `add-dock-apps.sh` file, or paste the Script content directly into the editor. Click **Create** to save the Script.

![add-dock-apps-script-form](https://docs.applivery.com/int/_r2/media/09ac0a4e-3ad8-478f-9f15-3474973eec71/0004b990-8fff-4609-bc96-e6236ce688ea.png)

## Add the Script to your macOS Policy

Next, go to the **Policies** 1 section and select the Policy assigned to your macOS Devices, or [create a new one](https://docs.applivery.com/en/device-management/general-settings/create-policies/). From the left-hand menu, select the **Scripts** 2 section.

:::warning
Scripts are only available for macOS Devices and require the **Applivery MDM Agent** to be enabled in the Policy. If it isn't enabled yet, go to the **Agent** section of the Policy and enable it before continuing.
:::

Click the **\+ Add Script** 3 button.

![policy script](https://docs.applivery.com/int/_r2/media/09ac0a4e-3ad8-478f-9f15-3474973eec71/d2d8cf0a-9084-49c6-84f6-555523c63c9a.png)

In the modal view, search for and select the **Add-DockApps-UserEditable** Script you created in the previous step. Set the **Execution method** to **Loop** and click **1d** next to the **Loop time** field. The field is filled with 86400 seconds, so the Script runs once a day. Leave **Arguments** empty and click **Add**.

![add-dock-apps-add-script-loop](https://docs.applivery.com/int/_r2/media/09ac0a4e-3ad8-478f-9f15-3474973eec71/2314a585-466b-464e-968b-fe1c3fcca2b9.png)

:::info
**Loop** is recommended over **Once**. The Script only acts on the user logged in at the console at the moment it runs, and only on Apps that are already installed. Running it every day picks up what a single run would miss: an App installed later, a Device that was at the login window, or another user of the same Mac. On Devices that are already set up, the daily run finds nothing pending and exits right away.
:::

The Script now appears in the list. Click **Save changes** to publish a new version of the Policy. The Script is sent to every Device assigned to the Policy.

![add-dock-apps-save-changes](https://docs.applivery.com/int/_r2/media/09ac0a4e-3ad8-478f-9f15-3474973eec71/ea973db0-0d1a-41d3-98ef-f090f7954291.png)

:::info
Before rolling it out to all your Devices, add the Script to a pilot Policy with one or two test Devices and check the result as described below.
:::

## Result on the Device

Once the Script has run, the user finds the Apps at the end of the Apps section of the Dock, right after the items that were already there. Nothing is removed or reordered.

From that point on, the Dock belongs to the user. They can reorder the new Apps, remove them, or add others as usual, and the Script won't undo those changes on its next runs.

## Verify the execution

Open the Device in the Applivery Dashboard, go to the **Scripts** tab, and click the **Execution history** button to see the execution history of the Script. You can learn more about it in **Scripts** by following this [link.](https://docs.applivery.com/en/device-management/apple/macos/scripts/)

:::warning
A run finishes successfully even when nothing was added, for example, because nobody was logged in or an App wasn't installed yet. Read the output of the run to know what happened. The Script only fails when an App couldn't be written to the Dock.
:::

This is an example of the output of a first run on a Device where Slack isn't installed yet:

```text
2026-11-09 09:12:40 [Dock] Not installed yet, will retry on next run: /Applications/Slack.app
2026-11-09 09:12:40 [Dock] Added: /Applications/Google Chrome.app
2026-11-09 09:12:40 [Dock] Added: /Applications/Microsoft Teams.app
2026-11-09 09:12:40 [Dock] Added: /Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app
2026-11-09 09:12:41 [Dock] Done for jane
```

And this is an example of the next run on the same Device, with Slack still not installed:

```text
2026-11-10 09:12:38 [Dock] Handled before, left as the user has it: /Applications/Google Chrome.app
2026-11-10 09:12:38 [Dock] Handled before, left as the user has it: /Applications/Microsoft Teams.app
2026-11-10 09:12:38 [Dock] Not installed yet, will retry on next run: /Applications/Slack.app
2026-11-10 09:12:38 [Dock] Handled before, left as the user has it: /Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app
2026-11-10 09:12:38 [Dock] Nothing to add for jane
```

## Troubleshooting

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Output</p></th><th colspan="1" rowspan="1"><p>Cause</p></th><th colspan="1" rowspan="1"><p>What to do</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>No user logged in, will retry on next run</code></p></td><td colspan="1" rowspan="1"><p>The Script ran with nobody logged in at the console: the Device was at the login window or still in Setup Assistant.</p></td><td colspan="1" rowspan="1"><p>Nothing. It's applied on a later run with a user logged in.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Not installed yet, will retry on next run</code></p></td><td colspan="1" rowspan="1"><p>The App isn't at the path set in the <code>APPS</code> list.</p></td><td colspan="1" rowspan="1"><p>Deploy the App to the Device, or correct the path in the Script.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Already in Dock</code></p></td><td colspan="1" rowspan="1"><p>The user already had the App in the Dock.</p></td><td colspan="1" rowspan="1"><p>Nothing. The Dock isn't changed.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Handled before, left as the user has it</code></p></td><td colspan="1" rowspan="1"><p>The App was handled on a previous run for this user.</p></td><td colspan="1" rowspan="1"><p>Nothing. This is what keeps the Dock editable.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Nothing to add for &lt;user&gt;</code></p></td><td colspan="1" rowspan="1"><p>No App is pending for this user.</p></td><td colspan="1" rowspan="1"><p>Nothing.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Dock not ready yet, will retry on next run</code></p></td><td colspan="1" rowspan="1"><p>The user's Dock wasn't running or ready after five minutes, usually during a first login.</p></td><td colspan="1" rowspan="1"><p>Nothing. It's applied on the next run.</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>Could not add, will retry on next run</code></p></td><td colspan="1" rowspan="1"><p>macOS rejected the change to the Dock. The run is reported as failed.</p></td><td colspan="1" rowspan="1"><p>Check the rest of the output for the error. The App is retried on the next run.</p></td></tr></tbody></table>

## Change the Apps later

To add more Apps to the Dock later, create a new Script with the updated `APPS` list, then replace the previous one in the Policy: in the **Scripts** section, click **Remove** on the old Script, add the new one with **\+ Add Script**, and click **Save changes**. Apps that were already handled for a user are left as that user has them, and only the new ones are added.

To add an App again for a user who removed it, delete its record on that Device. The record is a file with the name of the App inside the user's home folder. For example, run this as that user:

```bash
rm "$HOME/Library/Application Support/AppliveryDockSetup/Google Chrome.app"
```

The App is added again on the next run of the Script.

## Script

```bash
#!/bin/bash
# Adds apps to the Dock of the user logged in at the console, once per user and app.
# Keeps the existing Dock and leaves it unmanaged, so the user can still reorder or remove anything.
# Deployed from Applivery (runs as root). Safe to run in Loop: an app already handled for a
# user is never added again, and an app that is not installed yet is retried on a later run.
set -uo pipefail

# Edit this list (full paths)
APPS=(
    "/Applications/Google Chrome.app"
    "/Applications/Microsoft Teams.app"
    "/Applications/Slack.app"
    "/Applications/Utilities/Adobe Creative Cloud/ACC/Creative Cloud.app"
)

log() { echo "$(date '+%F %T') [Dock] $*"; }

USER_=$(stat -f '%Su' /dev/console)
if [ -z "$USER_" ] || [ "$USER_" = "root" ] || [ "$USER_" = "loginwindow" ] || [ "$USER_" = "_mbsetupuser" ]; then
    log "No user logged in, will retry on next run"; exit 0
fi
UID_=$(id -u "$USER_")
# The non-root branch only exists so you can test the script by hand in your own session
as_user() { if [ "$(id -u)" -eq 0 ]; then launchctl asuser "$UID_" sudo -H -u "$USER_" "$@"; else "$@"; fi; }

# One marker per app in the user's home: this is what makes a removal by the user stick
MARKS="$(as_user sh -c 'echo "$HOME"')/Library/Application Support/AppliveryDockSetup"

# Apps still to handle for this user: no marker yet and installed on the device
PENDING=()
for app in "${APPS[@]}"; do
    if [ -e "$MARKS/$(basename "$app")" ]; then
        log "Handled before, left as the user has it: $app"
    elif [ ! -e "$app" ]; then
        log "Not installed yet, will retry on next run: $app"
    else
        PENDING+=("$app")
    fi
done
[ ${#PENDING[@]} -eq 0 ] && { log "Nothing to add for $USER_"; exit 0; }

# The Dock must be running and have its own list of apps. On a first login macOS is still
# building it, and writing at that point would replace the default Dock instead of adding to it.
CURRENT=""
for _ in $(seq 1 60); do
    pgrep -u "$UID_" -x Dock >/dev/null \
        && CURRENT=$(as_user defaults read com.apple.dock persistent-apps 2>/dev/null) \
        && [ -n "$CURRENT" ] && break
    CURRENT=""; sleep 5
done
[ -z "$CURRENT" ] && { log "Dock not ready yet, will retry on next run"; exit 0; }

as_user mkdir -p "$MARKS"
CHANGED=0
FAILED=0
for app in "${PENDING[@]}"; do
    if grep -qF "file://${app// /%20}" <<<"$CURRENT" || grep -qF "\"$app\"" <<<"$CURRENT"; then
        log "Already in Dock: $app"
    elif as_user defaults write com.apple.dock persistent-apps -array-add \
        "<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>${app//&/&amp;}</string><key>_CFURLStringType</key><integer>0</integer></dict></dict><key>tile-type</key><string>file-tile</string></dict>"; then
        log "Added: $app"; CHANGED=1
    else
        log "Could not add, will retry on next run: $app"; FAILED=1; continue
    fi
    as_user touch "$MARKS/$(basename "$app")"
done

# The Dock only shows the new apps after it restarts (it blinks for a moment)
[ $CHANGED -eq 1 ] && as_user killall Dock
log "Done for $USER_"
exit $FAILED
```
