macOS Bluetooth Battery Alert – Full Setup Guide

Last Updated on December 11, 2025 by Freddy Reyes

This document explains, from scratch, how to set up a small background job on macOS that monitors the battery level of your Bluetooth input devices (mouse, keyboard, etc.). Whenever the lowest reported If the Bluetooth device battery drops below a threshold (10% by default), macOS will show a persistent notification (Alert) that stays on screen until you dismiss it.

Prerequisites:

  • A user account on macOS with admin rights.
  • Basic familiarity with Terminal (running commands, editing files).
  • Your Bluetooth mouse or other input device must expose its battery percentage to macOS (most Apple devices and many third-party devices do)

1. Check that macOS sees Bluetooth Battery Levels

Open Terminal and run the following command to see all Bluetooth devices that report a BatteryPercent value:

ioreg -r -k BatteryPercent | awk '/"BatteryPercent"/ { gsub(/[^0-9]/,"",$3); count++; print count "

You should see output such as “1: 37%”, “2: 85%”, one line per device. The script we’ll create will look at the lowest of these values and compare it to your threshold.

2. Create the Battery Check Script

We’ll create a shell script that checks the lowest Bluetooth device battery level and, if it is below a threshold, asks macOS to show a notification. This example uses a 10% threshold and sends a Notification Center alert.

2.1. Create the folder (if it does not already exist) and the script file:

mkdir -p "/Users/username/bin"
cd "/Users/username/bin"
nano mouse_battery_check.sh

2.2. Paste the following full script into the editor:

#!/bin/bash
THRESHOLD=10
BATTERY=$(ioreg -r -k BatteryPercent | awk '
/"BatteryPercent"/ {
    gsub(/[^0-9]/, "", $3)
    v = $3 + 0
    if (min == "" || v < min) min = v
}
END {
    if (min != "") print min
}')
if [[ "$BATTERY" != "" && "$BATTERY" -lt "$THRESHOLD" ]]; then
  osascript -e "display alert \"A Bluetooth device battery is at $BATTERY%\""
fi

Save and exit the editor (for nano: press Ctrl+O, Enter, then Ctrl+X). Then make the script executable:

chmod +x "/Users/username/bin/mouse_battery_check.sh"

3. Create the LaunchAgent to Run the Script Automatically

We use a LaunchAgent so that macOS will periodically run the script in the background while you are logged in. The agent will run the script every 300 seconds (5 minutes).

3.1. Create the LaunchAgents folder (it normally already exists) and the plist file:

mkdir -p ~/Library/LaunchAgents
nano ~/Library/LaunchAgents/com.user.mousebattery.plist

3.2. Paste this complete plist content:

<?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.user.mousebattery</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>/Users/username/bin/mouse_battery_check.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>300</integer>
    <key>StandardOutPath</key>
    <string>/Users/username/mousebattery.out</string>
    <key>StandardErrorPath</key>
    <string>/Users/username/mousebattery.err</string>
</dict>
</plist>

Save and exit the editor. Then validate that the plist is well formed XML so launchd will accept it:

plutil -lint ~/Library/LaunchAgents/com.user.mousebattery.plist

4. Load the LaunchAgent into launchd

On modern macOS versions, you should use launchctl bootstrap for user LaunchAgents. First, remove any stale instance of the job (this is safe even if it was not loaded), then bootstrap the new one:

launchctl bootout "gui/$(id -u)/com.user.mousebattery" 2>/dev/null

launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.user.mousebattery.plist

f there is no error message, the job is now registered. You can confirm that launchd knows about it with:

launchctl print "gui/$(id -u)" | grep mousebattery -A5

5. Test the Script and Job

First, test the script directly to make sure it runs without errors. Temporarily raise the threshold so it will definitely trigger a notification (for example, set THRESHOLD=100 in the script), then run:

"/Users/username/bin/mouse_battery_check.sh"

You should see a macOS notification titled “Low Battery”. Once you confirm it works, set THRESHOLD back to 10. To make launchd run the job immediately (instead of waiting for the next 5 minute interval), use:

launchctl kickstart -k "gui/$(id -u)/com.user.mousebattery"

6. Make Notifications Persistent (Alerts, Not Banners)

By default, macOS may show the notification as a temporary banner that disappears after a few seconds. To turn it into a persistent alert that stays on screen until you dismiss it:

  • Run the script once so you definitely see a notification.
  • Open System Settings → Notifications.
  • In the list of apps on the right, look for the app associated with the notification (often “Script Editor” or similar, depending on your macOS version).
  • Click that app, then under Alert Style choose “Alerts” instead of “Banners”.
  • Make sure “Allow Notifications” is enabled for that app.

7. Troubleshooting

Here are some common issues and how to resolve them:

• Bootstrap failed: 5: Input/output error

  • Usually means launchd already has a job with that Label or the plist path is wrong.
  • Run: launchctl bootout “gui/$(id -u)/com.user.mousebattery” and then bootstrap again.
  • Exit status 127 in launchctl print output
  • Indicates “command not found”; typically the script path in ProgramArguments is wrong.
  • Confirm that the path in the plist exactly matches the real script location.
  • No mousebattery.err or mousebattery.out files
  • If the job has never produced output or failed, the files may not exist yet.
  • After a failure, check: cat ~/mousebattery.err and cat ~/mousebattery.out for details.

• No notification appears when running the script

  • Temporarily set THRESHOLD=100 and run the script manually.
  • Make sure at least one Bluetooth device is connected and reports a battery percentage.