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
- In Jamf Pro, click Settings in the sidebar.
- Under Computer management, click Extension attributes, then New.
- Enter a display name such as “Free space (GB)” and a description.
- Set Data Type to Integer and choose where it appears in inventory (the Inventory Display setting, for example Hardware or Operating System).
- Set Input Type to Script and paste the script.
- 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:
- Go to Computers > Smart Computer Groups > New.
- On the Criteria tab, add your attribute, for example “Free space (GB)”.
- Choose the operator less than and a value such as
20. - 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-bytesandpurgeable-bytesfor the startup diskcloud-locations, the number of cloud storage folders on the Maclast-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 readlastScanAtfrom 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.
