What is xbar?
xbar is a free and open-source MacOS application that lets you put the output of any script or program into your Mac menu bar. With just a few lines of shell script, you can display system information, check email, monitor server status, or create custom utilities.
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
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:
defaults -currentHost write -globalDomain NSStatusItemSpacing -int 6Increase 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.
Understanding xbar Plugin Format
Filename Convention
xbar uses the filename to determine refresh intervals:
plugin-name.{time}.{extension}Refresh intervals:
1s- Every second10s- Every 10 seconds1m- Every minute5m- Every 5 minutes1h- Every hour1d- Every day
Examples:
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
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
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
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
}Duration Parsing:
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
}Fixed-width Display:
# 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:
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
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:
plugin-name.INTERVAL.extensionFor example:
timer.1s.sh(refreshes every second)
Advanced Examples
API Integration
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
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
Start with simple plugins and gradually build more complex ones as you learn. The xbar community has created hundreds of plugins you can use as references and inspiration.