What is xbar?
Key Features
- 🚀 Simple: Write scripts in any language (bash, Python, JavaScript, etc.)
- 🔄 Auto-refresh: Set custom refresh intervals
- 📦 Plugin Repository: Browse hundreds of community plugins
- ⚙️ Customizable: Full control over appearance and behavior
Installation
Method 1: Homebrew
brew install --cask xbarMethod 2: Direct Download
Download the latest release from xbarapp.com and drag it to your Applications folder.Browse and Install Plugins

- Open xbar and click on the menu bar icon
- Select "Plugin Browser"
- Browse or search for plugins
- Click "Install" to add a plugin
Tip
If you have many plugins installed, menu bar icons may overflow.Change the spacing between icons in menu bar by running the following command in Terminal:Increase the number to widen the spacing, decrease to narrow it. You need to sign out and sign in again for the change to take effect.
defaults -currentHost write -globalDomain NSStatusItemSpacing -int 6Understanding xbar Plugin Format
Filename Convention
xbar uses the filename to determine refresh intervals:plugin-name.{time}.{extension}1s- Every second10s- Every 10 seconds1m- Every minute5m- Every 5 minutes1h- Every hour1d- Every day
weather.5m.sh # Refreshes every 5 minutes
email.1m.py # Refreshes every minute
stocks.30s.js # Refreshes every 30 secondsOutput Format
xbar plugins use a simple text-based format:# Menu bar display (first line)
echo "Display Text"
# Dropdown menu (lines after "---")
echo "---"
echo "Menu Item 1"
echo "Menu Item 2"Script Examples
Let's examine the three xbar scripts:1. Email Unread Count Monitor
email_unread.1m.sh
1#!/bin/bash
2# <xbar.title>Mail</xbar.title>
3# <xbar.desc>Show unread count from Mail.app in menubar (with Open Mail option)</xbar.desc>
4# <xbar.dependencies>bash,osascript,open</xbar.dependencies>
5set -e
6
7# get unread count (fall back to 0 if empty)
8OUTPUT=$(osascript -e 'tell application "Mail"' -e 'unread count of inbox' -e 'end tell')
9OUTPUT=${OUTPUT:-0}
10
11echo -n "📥"
12echo " ${OUTPUT}"
13
14echo "---"
15
16if [[ $OUTPUT -gt 0 ]]
17then
18 echo "Open Mail | bash=/usr/bin/open param1=-a param2=Mail terminal=false"
19else
20 echo "No unread mail"
21fiHow It Works
- AppleScript Integration: Uses
osascriptto query Mail.app's unread count - Menu Bar Display: Shows 📥 icon with unread count
- Interactive Menu: Provides "Open Mail" button when unread messages exist
- Refresh: Updates every minute (
1min filename)
Features
- ✅ Quick glance at unread emails without opening Mail.app
- ✅ Click to open Mail.app directly
- ✅ Handles Mail.app not running gracefully
2. USB-C Charger Wattage Display
power_wattage.30s.sh
1#!/bin/bash
2# <xbar.title>USB-C Charger Wattage with Battery</xbar.title>
3# <xbar.desc>Displays current USB-C charger wattage and battery percentage.</xbar.desc>
4
5# Get battery percentage
6BATTERY=$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)
7
8# Get charger wattage
9WATTAGE=$(system_profiler SPPowerDataType | grep "Wattage" | awk '{print $3}')
10
11if [ -z "$WATTAGE" ]; then
12 # On battery
13 echo "🔋 ${BATTERY}%"
14else
15 # Charging
16 echo "⚡ ${WATTAGE}W ${BATTERY}%"
17fiHow It Works
- Battery Status: Uses
pmsetto get battery percentage - Charger Detection: Uses
system_profilerto detect connected charger wattage - Smart Display: Shows different icons for battery vs charging state
- Refresh: Updates every 30 seconds
Use Cases
- Monitor charging power, e.g., ⚡ 65W (useful for checking if using correct charger)
- Battery level, e.g., 🔋 50%
3. Advanced Countdown Timer
simple-timer.1s.sh
1#!/bin/bash
2# <xbar.title>Simple Timer (Fixed-width)</xbar.title>
3# <xbar.version>v1.2</xbar.version>
4# <xbar.author>Kensuke</xbar.author>
5# <xbar.desc>Countdown timer with fixed-width digits (uses fullwidth Unicode digits).</xbar.desc>
6# <xbar.dependencies>bash, osascript</xbar.dependencies>
7# <xbar.refresh>1</xbar.refresh>
8
9STATE_FILE="/tmp/xbar_timer_state"
10
11now() { date +%s; }
12
13save_state() {
14 cat >"$STATE_FILE" <<EOF
15END_TIME=$END_TIME
16RUNNING=$RUNNING
17REMAINING=$REMAINING
18NOTIFIED=$NOTIFIED
19EOF
20}
21
22load_state() {
23 if [ -f "$STATE_FILE" ]; then
24 source "$STATE_FILE"
25 else
26 END_TIME=0
27 RUNNING=0
28 REMAINING=0
29 NOTIFIED=0
30 fi
31}
32
33# convert ASCII digits and colon to fullwidth characters (fixed width)
34to_fullwidth() {
35 s="$1"
36 # array of fullwidth digits 0-9
37 digits=(0 1 2 3 4 5 6 7 8 9)
38 for i in 0 1 2 3 4 5 6 7 8 9; do
39 s="${s//${i}/${digits[i]}}"
40 done
41 # convert colon to fullwidth colon
42 s="${s//:/:}"
43 echo "$s"
44}
45
46# parse human-friendly durations: 25m, 5m, 1m, 90s, 1h, or plain minutes (e.g. 25)
47parse_seconds() {
48 arg="$1"
49 if [[ "$arg" =~ ^([0-9]+)h$ ]]; then
50 echo $(( ${BASH_REMATCH[1]} * 3600 ))
51 elif [[ "$arg" =~ ^([0-9]+)m$ ]]; then
52 echo $(( ${BASH_REMATCH[1]} * 60 ))
53 elif [[ "$arg" =~ ^([0-9]+)s$ ]]; then
54 echo "${BASH_REMATCH[1]}"
55 elif [[ "$arg" =~ ^[0-9]+$ ]]; then
56 # treat plain number as minutes
57 echo $(( arg * 60 ))
58 else
59 # fallback: 60s
60 echo 60
61 fi
62}
63
64notify_finished() {
65 /usr/bin/osascript -e 'beep 3'
66 /usr/bin/osascript -e 'display dialog "Timer finished!" with title "xbar Timer" buttons {"OK"} default button "OK" with icon caution' &>/dev/null
67}
68
69# Handle action invocations
70if [ "$1" = "start" ]; then
71 DURATION_STR="${2:-25m}"
72 DURATION=$(parse_seconds "$DURATION_STR")
73 END_TIME=$(( $(now) + DURATION ))
74 RUNNING=1
75 REMAINING=0
76 NOTIFIED=0
77 save_state
78 exit 0
79elif [ "$1" = "custom" ]; then
80 # Show input dialog for custom timer
81 CUSTOM_INPUT=$(/usr/bin/osascript -e 'display dialog "Enter timer duration:\n(e.g., 10m, 30s, 1h, or just 15 for minutes)" default answer "10m" with title "Custom Timer" buttons {"Cancel", "Start"} default button "Start"' -e 'text returned of result' 2>/dev/null)
82 if [ -n "$CUSTOM_INPUT" ]; then
83 DURATION=$(parse_seconds "$CUSTOM_INPUT")
84 END_TIME=$(( $(now) + DURATION ))
85 RUNNING=1
86 REMAINING=0
87 NOTIFIED=0
88 save_state
89 fi
90 exit 0
91elif [ "$1" = "stop" ]; then
92 load_state
93 if [ "$RUNNING" -eq 1 ]; then
94 REM=$(( END_TIME - $(now) ))
95 if [ "$REM" -lt 0 ]; then REM=0; fi
96 REMAINING=$REM
97 END_TIME=0
98 fi
99 RUNNING=0
100 NOTIFIED=0
101 save_state
102 exit 0
103elif [ "$1" = "reset" ]; then
104 END_TIME=0
105 RUNNING=0
106 REMAINING=0
107 NOTIFIED=0
108 save_state
109 exit 0
110fi
111
112# Normal display run (no args)
113load_state
114
115# Determine display text and icon
116if [ "$RUNNING" -eq 1 ] && [ "$END_TIME" -gt 0 ]; then
117 REM=$(( END_TIME - $(now) ))
118 if [ "$REM" -eq 0 ]; then
119 # Show 00:00 first
120 DISPLAY="00:00"
121 ICON="⏰"
122 elif [ "$REM" -lt 0 ]; then
123 # Time is up, show alert
124 if [ "$NOTIFIED" -eq 0 ]; then
125 notify_finished
126 NOTIFIED=1
127 fi
128 RUNNING=0
129 REMAINING=0
130 END_TIME=0
131 save_state
132 DISPLAY="Done"
133 ICON=""
134 else
135 # Running timer - format time
136 H=$(( REM / 3600 ))
137 M=$(( (REM % 3600) / 60 ))
138 S=$(( REM % 60 ))
139 if [ "$H" -gt 0 ]; then
140 printf -v DISPLAY "%02d:%02d:%02d" "$H" "$M" "$S"
141 else
142 printf -v DISPLAY "%02d:%02d" "$M" "$S"
143 fi
144 ICON="⏰"
145 fi
146else
147 # Stopped/paused timer
148 REM=$REMAINING
149 if [ "$REM" -gt 0 ]; then
150 # Paused with remaining time
151 H=$(( REM / 3600 ))
152 M=$(( (REM % 3600) / 60 ))
153 S=$(( REM % 60 ))
154 if [ "$H" -gt 0 ]; then
155 printf -v DISPLAY "%02d:%02d:%02d" "$H" "$M" "$S"
156 else
157 printf -v DISPLAY "%02d:%02d" "$M" "$S"
158 fi
159 ICON="⏸"
160 else
161 # No timer set
162 DISPLAY="⏰"
163 ICON=""
164 fi
165fi
166
167# Convert to fullwidth if contains digits/colon
168if [[ "$DISPLAY" =~ [0-9] ]]; then
169 DISPLAY=$(to_fullwidth "$DISPLAY")
170fi
171
172# Combine icon and display
173if [ -n "$ICON" ]; then
174 DISPLAY="${ICON} ${DISPLAY}"
175fi
176
177# Single output point for menu bar
178echo "$DISPLAY"
179
180# Menu items
181echo '---'
182SCRIPT_PATH="$0"
183echo "Start 1h | bash='$SCRIPT_PATH' param1='start' param2='1h' terminal=false refresh=true"
184echo "Start 30m | bash='$SCRIPT_PATH' param1='start' param2='30m' terminal=false refresh=true"
185echo "Start 3m | bash='$SCRIPT_PATH' param1='start' param2='3m' terminal=false refresh=true"
186echo "Set Custom Timer | bash='$SCRIPT_PATH' param1='custom' terminal=false refresh=true"
187if [ "$RUNNING" -eq 1 ]; then
188 echo "Stop | bash='$SCRIPT_PATH' param1='stop' terminal=false refresh=true"
189fi
190echo "Reset | bash='$SCRIPT_PATH' param1='reset' terminal=false refresh=true"Advanced Features
This timer script demonstrates several advanced xbar techniques:- State Persistence: Uses
/tmp/xbar_timer_stateto maintain timer state between refreshes - Full-width Unicode: Converts digits to full-width characters (0123) for consistent spacing
- Interactive Controls: Provides start, stop, and reset buttons in dropdown menu
- Flexible Duration Input: Accepts formats like
25m,90s,1h, or plain numbers (treated as minutes) - Custom Timer Dialog: Shows macOS dialog for entering custom durations
- Notifications: Audio beep and visual dialog when timer completes
Key Implementation Details
State Management:# Save timer state to file
save_state() {
cat >"$STATE_FILE" <<EOF
END_TIME=$END_TIME
RUNNING=$RUNNING
REMAINING=$REMAINING
NOTIFIED=$NOTIFIED
EOF
}parse_seconds() {
arg="$1"
if [[ "$arg" =~ ^([0-9]+)h$ ]]; then
echo $(( ${BASH_REMATCH[1]} * 3600 ))
elif [[ "$arg" =~ ^([0-9]+)m$ ]]; then
echo $(( ${BASH_REMATCH[1]} * 60 ))
# ... more formats
}# Converts "12:34" to "12:34" for consistent menu bar width
to_fullwidth() {
digits=(0 1 2 3 4 5 6 7 8 9)
# ... conversion logic
}Advanced xbar Plugin Techniques
Menu Item Actions
xbar supports interactive menu items with various actions:# Open URL
echo "GitHub | href=https://github.com"
# Run bash command
echo "Restart | bash=/usr/bin/killall param1=SystemUIServer terminal=false refresh=true"
# Run with parameters
echo "Process File | bash='./process.sh' param1='file.txt' terminal=false"
# Show in terminal
echo "Run Script | bash='./script.sh' terminal=true"
# Refresh after action
echo "Action | bash='command' refresh=true"Text Formatting
# Colors
echo "Red Text | color=red"
echo "Custom Color | color=#FF5733"
# Font styles
echo "Bold Text | font=Monaco size=14"
echo "Custom Font | font='Helvetica Neue' size=12"
# Combine attributes
echo "Styled Item | color=blue font=Monaco size=14"Icons and Emojis
# Use emojis
echo "⚠️ Warning"
echo "✅ Success"
echo "📧 Email"
# Use SF Symbols (macOS 11+)
echo "Symbol | sfimage=star.fill"
# Custom image
echo "Item | image=base64encodedimage"Submenus
echo "Parent Menu"
echo "--Submenu Item 1"
echo "--Submenu Item 2"
echo "----Nested Item"Separators
echo "---" # Separator lineWriting Your Own xbar Plugin
Let's create a simple weather plugin:weather.30m.sh
1#!/bin/bash
2
3# Get weather data (using wttr.in API)
4LOCATION="Tokyo"
5WEATHER=$(curl -s "wttr.in/${LOCATION}?format=%c+%t" | head -n 1)
6
7# Menu bar display
8echo "🌡️ ${WEATHER}"
9
10# Dropdown menu
11echo "---"
12echo "Location: ${LOCATION}"
13echo "---"
14echo "Refresh | refresh=true"
15echo "Open wttr.in | href=https://wttr.in/${LOCATION}"Steps to Install
-
Create the file:
touch ~/plugins/weather.30m.sh -
Make it executable:
chmod +x ~/plugins/weather.30m.sh -
Refresh xbar:
- Click xbar menu → "Refresh All"
Best Practices
1. Error Handling
#!/bin/bash
set -e # Exit on error
# Handle missing commands
if ! command -v jq &> /dev/null; then
echo "❌ Error"
echo "---"
echo "jq not installed"
exit 1
fi2. Performance
Warning
Avoid slow operations in frequently refreshed plugins. For example, don't run a plugin that makes network requests every second.
# Cache results for expensive operations
CACHE_FILE="/tmp/xbar_cache_${PLUGIN_NAME}"
CACHE_TTL=300 # 5 minutes
if [ -f "$CACHE_FILE" ]; then
AGE=$(($(date +%s) - $(stat -f %m "$CACHE_FILE")))
if [ $AGE -lt $CACHE_TTL ]; then
cat "$CACHE_FILE"
exit 0
fi
fi
# Fetch new data
DATA=$(expensive_operation)
echo "$DATA" | tee "$CACHE_FILE"3. Security
# Don't hardcode sensitive data
API_KEY="${MY_API_KEY:-}"
if [ -z "$API_KEY" ]; then
echo "❌ No API key"
exit 1
fi
# Use environment variables or keychain
PASSWORD=$(security find-generic-password -s "my-service" -w)Plugin Ideas
Here are some ideas for custom xbar plugins:System Monitoring
- CPU/Memory usage
- Disk space
- Network speed
- Running processes
Productivity
- Simer ✅ (already have this!)
- Task list from Things/Todoist
- Calendar events
- Clipboard history
Communication
- Unread messages (Slack, Discord)
- Email count ✅ (already have this!)
- GitHub notifications
Development
- Git repository status
- CI/CD build status
- Server uptime
- Docker container status
Personal
- Bitcoin price
- Stock prices
- Weather ✅ (example above)
- Calendar events
- Habit tracker
Troubleshooting
-
Check permissions:
chmod +x ~/plugins/your-plugin.sh -
Verify shebang:
#!/bin/bash # Must be first line -
Check syntax:
bash -n your-plugin.sh -
Filename format:
For example:
plugin-name.INTERVAL.extensiontimer.1s.sh(refreshes every second)
Advanced Examples
API Integration
github-notifications.5m.sh
1#!/bin/bash
2
3GITHUB_TOKEN="${GITHUB_TOKEN}"
4API_URL="https://api.github.com/notifications"
5
6COUNT=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" | jq length)
7
8if [ "$COUNT" -gt 0 ]; then
9 echo "🔔 ${COUNT}"
10else
11 echo "🔕"
12fi
13
14echo "---"
15echo "GitHub Notifications | href=https://github.com/notifications"System Information
system-info.1h.sh
1#!/bin/bash
2
3# Get system info
4UPTIME=$(uptime | awk '{print $3,$4}' | sed 's/,//')
5CPU=$(top -l 1 | grep "CPU usage" | awk '{print $3}')
6MEMORY=$(top -l 1 | grep "PhysMem" | awk '{print $2}')
7
8echo "💻 System"
9echo "---"
10echo "Uptime: ${UPTIME}"
11echo "CPU: ${CPU}"
12echo "Memory: ${MEMORY}"Resources
Conclusion
xbar is a powerful tool for customizing your Mac menu bar. With just a few lines of shell script, you can:- 📊 Monitor system resources
- 📧 Check email and notifications
- ⏱️ Create productivity tools
- 🌐 Integrate with APIs