Disk space reporting in Jamf with extension attributes

Photo: Josh Sorenson on Pexels

Jamf Pro already knows each Mac's disk size, but not in a form you can easily alert on. A small extension attribute script fixes that. Here is a ready-made one using only built-in tools, and a version that adds what is filling the disk.

Updated October 2026 6 min readmacOS 15 to 27

How extension attributes work

An extension attribute adds a field to each computer’s inventory in Jamf Pro. With the Script input type, Jamf runs your script on the Mac during every inventory update and stores what it prints.

The rules for the script are short:

  • It runs as root, through the Jamf binary, whenever the Mac submits inventory: usually from a policy with Update Inventory turned on, or when someone runs jamf recon.
  • It must print its value wrapped in a result tag: <result>value</result>. Anything outside the tags is ignored.
  • The Data Type you choose decides how Jamf compares values: String, Integer, or Date (YYYY-MM-DD hh:mm:ss). Use Integer for sizes, so Smart Groups can use “less than” and “more than”.
  • It should be fast. Every extension attribute adds time to every inventory update, so keep slow work out of it.

A free space attribute with built-in tools

This script needs nothing but macOS. It reports free space on the startup disk in whole gigabytes (1 GB is 1,000,000,000 bytes, the way Finder counts):

#!/bin/sh
# Free space on the startup disk in GB, for a Jamf Pro
# extension attribute with Data Type: Integer.
kb=$(df -k / | awk 'NR == 2 { print $4 }')
if [ -n "$kb" ]; then
  echo "<result>$(( kb * 1024 / 1000000000 ))</result>"
else
  echo "<result></result>"
fi

On APFS, every volume in a container shares the same free space, so / gives the right number even though your files live on the Data volume.

One difference from what users see: this number does not include purgeable space, which macOS can free on demand. Storage settings and Finder add purgeable space to “available”, so their number can be higher. For alerting, the lower number is the honest one.

Add it to Jamf Pro

  1. In Jamf Pro, click Settings in the sidebar.
  2. Under Computer management, click Extension attributes, then New.
  3. Enter a display name such as “Free space (GB)” and a description.
  4. Set Data Type to Integer and choose where it appears in inventory (the Inventory Display setting, for example Hardware or Operating System).
  5. Set Input Type to Script and paste the script.
  6. Click Save.

Values appear as Macs submit inventory. To test on one Mac right away, run sudo jamf recon in Terminal, then check the computer’s inventory record.

Smart Groups and alerts

With an Integer attribute you can build a Smart Group:

  1. Go to Computers > Smart Computer Groups > New.
  2. On the Criteria tab, add your attribute, for example “Free space (GB)”.
  3. Choose the operator less than and a value such as 20.
  4. Turn on Send email notification on membership change if you want to hear when a Mac joins the group.

Two thresholds work well in practice: one around 20 GB for a warning, and one around 10 GB for Macs that will soon fail to install updates. Major macOS updates need considerably more free space than that, so a group for “less than 30 GB” before a major update rollout saves support tickets. You can also scope a policy to the group, for example one that shows a message through Self Service or a notification.

Go further with dux

Free space tells you which Macs are running low. It does not tell you why. dux for Business, which uses the direct download, includes a command line helper that reports both, in the same <result> format, so it drops straight into an extension attribute.

The helper lives inside the app bundle, so scripts work even when people never install the command line link:

#!/bin/sh
# Purgeable space in bytes (Data Type: Integer).
DUX="/Applications/dux.app/Contents/Helpers/dux"
[ -x "$DUX" ] || { echo "<result></result>"; exit 0; }
"$DUX" report --extension-attribute purgeable-bytes

dux report --extension-attribute takes one field and prints <result>value</result>. The fields that answer right away, without a scan, are:

  • capacity-bytes, used-bytes, free-bytes and purgeable-bytes for the startup disk
  • cloud-locations, the number of cloud storage folders on the Mac
  • last-scan, the start of the latest scan saved by the account that runs the command, in ISO 8601 UTC. Extension attributes run as root, which has no saved scans, so read lastScanAt from the saved report instead (see below)

Two more, reclaimable-bytes (an estimate of what cleanups could free) and largest-folder, run a scan first. Sizes are in bytes, and a value that is not available prints an empty <result></result>.

Reclaimable space, without slowing inventory

The pattern that scales is a daily policy that scans in the background and saves a report, and an extension attribute that only reads that file.

The policy script runs dux report --json as the person who is logged in, so it scans their home folder with their settings, and saves the result where root can read it:

#!/bin/sh
# Jamf policy, once per day: save a dux report for the
# logged-in user.
DUX="/Applications/dux.app/Contents/Helpers/dux"
OUT="/Library/Application Support/dux-report"
user=$(stat -f%Su /dev/console)
[ -n "$user" ] && [ "$user" != "root" ] || exit 0
mkdir -p "$OUT"
launchctl asuser "$(id -u "$user")" sudo -u "$user" \
  "$DUX" report --json > "$OUT/report.json.tmp" &&
  mv "$OUT/report.json.tmp" "$OUT/report.json"

The extension attribute then reads one value from the saved JSON with plutil, which ships with macOS:

#!/bin/sh
# Reclaimable space in GB from the last dux report
# (Data Type: Integer).
f="/Library/Application Support/dux-report/report.json"
v=$(plutil -extract scan.reclaimableBytes raw -o - "$f" 2>/dev/null)
if [ -n "$v" ]; then
  echo "<result>$(( v / 1000000000 ))</result>"
else
  echo "<result></result>"
fi

The report also contains volume.freeBytes, volume.purgeableBytes, lastScanAt, the reclaimable bytes per category in scan.reclaimableByCategory (developer files, caches, downloads and so on) and the ten largest folders. The JSON carries a schemaVersion, so your scripts can check it.

lastScanAt is in ISO 8601 UTC, such as 2026-10-29T09:14:00Z. A Jamf Date attribute needs YYYY-MM-DD hh:mm:ss, so convert it:

#!/bin/sh
# Date of the last dux scan, in UTC (Data Type: Date).
f="/Library/Application Support/dux-report/report.json"
v=$(plutil -extract lastScanAt raw -o - "$f" 2>/dev/null)
if [ -n "$v" ]; then
  d=$(date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$v" "+%Y-%m-%d %H:%M:%S")
  echo "<result>$d</result>"
else
  echo "<result></result>"
fi

Grant dux Full Disk Access with a Privacy Preferences Policy Control profile; without it, the scan skips folders macOS protects and the estimate is lower. The deployment guide for Business includes a sample profile.

Exit codes

The helper uses stable exit codes, which is useful when a policy log shows a failure: 0 success, 1 error, 2 usage error, 3 blocked by an administrator policy, and 4 when the Mac has no license that includes fleet reporting. Fleet reporting is part of the Business plan, activated through the LicenseKey managed setting.

Reporting never changes anything on the Mac. If you also want to make sure users cannot clean up through dux, set ReportOnly in the same configuration profile.

Was this guide helpful?