SwiftBar & Xbar plugin

The Meta Package Manager project is actively maintaining a plugin that is both compatible with SwiftBar and Xbar.

The plugin is written in Python and is a small wrapper around the mpm CLI. It is one of the two desktop menus the project maintains, and that page holds what the two have in common.

SwiftBar renders the version-diff colors natively (package lines carry the ansi=true parameter); Xbar strips the color codes and shows plain text. On the translucent menus of recent macOS releases the suffixes adapt to the menu appearance, since the default system colors lose contrast against the material: a light menu darkens both suffixes, and a dark menu brightens the green.

Hint

I recommend SwiftBar, because Xbar has 2 outstanding issues:

Important

SwiftBar 2.1.0 or newer is required: older releases mangle the variable defaults carrying an = sign (swiftbar/SwiftBar#445). The plugin detects the host version and renders an error instead of its menu below that threshold. Xbar exposes no version to its plugins, so it is not checked.

Configuration

The plugin is configurable with these environment variables:

Variable name

Description

Type

Defaults

SwiftBar support

Xbar support

VAR_GROUP_BY_MANAGER

Group each manager’s packages into a section of its own.

Boolean

False

VAR_TABLE_RENDERING

Aligns package names and versions in a table for easier visual parsing.

Boolean

True

VAR_MAX_VERSION_WIDTH

Widest a version renders in a menu line, in characters.

Number

18

VAR_DEFAULT_FONT

Font parameters for regular text.

String

Empty

❌*

VAR_MONOSPACE_FONT

Font parameters for monospace text. Used for table rendering and error messages.

String

font=Menlo size=12

❌*

VAR_HIDE_WHEN_UP_TO_DATE

Hide the menu bar icon while nothing is outdated and no manager errored.

Boolean

False

Note

SwiftBar renders two things differently from Xbar: the outdated count sits in a native badge on each manager header rather than in its label, and the grouped layout folds every section into an inline accordion (swiftbar/SwiftBar#480) that expands in place without dismissing the menu. Both are visible in the screenshots below.

These variables only drive the menu layout: everything else comes from mpm’s own configuration, as Desktop menus explains.

A macOS menu cannot scroll sideways, so VAR_MAX_VERSION_WIDTH keeps a row from outgrowing it. A version longer than the cap is shortened with an , always on the side the two versions have in common, so the characters telling them apart stay on screen. SwiftBar carries the untruncated pair in the item tooltip. Homebrew casks are what makes this necessary: their version,revision pairs run to 50 characters, against 17 for the longest version anyone reads. Set the variable to 0 to render every version whole.

Screenshots

Each layout is captured in both system appearances, and the tabs are synchronized so switching one switches the others. The menu chrome is the host’s, and only the version diff is the plugin’s: its colors have to stay legible on either appearance, which is what the palette of menu_diff_colors() is picked for.

SwiftBar

VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = False
/_images/swiftbar-flat-standard-rendering-light.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = True
(default)
/_images/swiftbar-flat-table-rendering-light.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = True
/_images/swiftbar-grouped-table-rendering-light.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = False
/_images/swiftbar-grouped-standard-rendering-light.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = False
/_images/swiftbar-flat-standard-rendering-dark.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = True
(default)
/_images/swiftbar-flat-table-rendering-dark.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = True
/_images/swiftbar-grouped-table-rendering-dark.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = False
/_images/swiftbar-grouped-standard-rendering-dark.png

Xbar

VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = False
/_images/xbar-flat-standard-rendering-light.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = True
(default)
/_images/xbar-flat-table-rendering-light.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = True
/_images/xbar-grouped-table-rendering-light.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = False
/_images/xbar-grouped-standard-rendering-light.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = False
/_images/xbar-flat-standard-rendering-dark.png
VAR_GROUP_BY_MANAGER = False
VAR_TABLE_RENDERING = True
(default)
/_images/xbar-flat-table-rendering-dark.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = True
/_images/xbar-grouped-table-rendering-dark.png
VAR_GROUP_BY_MANAGER = True
VAR_TABLE_RENDERING = False
/_images/xbar-grouped-standard-rendering-dark.png

Location

Once mpm is installed on your system, the plugin ships with it and its path is printed by the dedicated --bar-plugin-path option:

$ mpm --bar-plugin-path
~/Library/Python/3.11/lib/python/site-packages/meta_package_manager/bar_plugin.py

Symlinking that path into the host’s plugin folder is what keeps the menu on the latest plugin: every mpm upgrade is then picked up with no further action. That is how my dotfiles install it.

Where that folder is, and how it is set, differs between the two hosts:

SwiftBar has no default plugin folder: it asks for one on first launch, then imports every file it finds there, traversing nested folders and symlinks.

~/.swiftbar is the conventional choice:

$ mkdir -p ~/.swiftbar
$ ln -sf "$(mpm --bar-plugin-path)" ~/.swiftbar/meta_package_manager.7h.py

SwiftBar’s folder picker hides dotted folders, so point the app at it either by typing the path into that picker, or by writing the preference by hand. A running SwiftBar rewrites its preferences from memory when it quits, so that write only sticks while the app is stopped:

$ killall SwiftBar
$ defaults write com.ameba.SwiftBar PluginDirectory -string "${HOME}/.swiftbar"
$ open -a SwiftBar

Warning

Do not use ~/Library/Application Support/SwiftBar/Plugins, tempting as the name is. That is SwiftBar’s own data directory, in which it keeps a state and a cache folder per plugin (swiftbar/SwiftBar#522). Pointing the plugin folder at it makes SwiftBar scan the tree it writes into, and import its own output as plugins.

Important

~/.swiftbar needs SwiftBar 2.1.1 or newer. 2.1.0 introduced packaged .swiftbar plugin bundles and recognized them by the folder name, so it took the whole .swiftbar root for one bundle and skipped every file inside it as not a regular file (swiftbar/SwiftBar#508).

Xbar’s plugin folder is fixed at ~/Library/Application Support/xbar/plugins:

$ ln -sf "$(mpm --bar-plugin-path)" "${HOME}/Library/Application Support/xbar/plugins/meta_package_manager.7h.py"

A copy of the latest stable release is also published on the Xbar website and in its plugin repository, so it can be installed from Xbar’s own plugin browser. That copy is only refreshed on mpm releases, so it lags behind the symlink above.

Which mpm the plugin runs

The plugin prefers the mpm it is part of. It ships inside the package, so it walks back up its own folders to the installation that carries it, and runs that one: the version it was released with, whose dependencies are already resolved. The symlink above is followed to get there, or the walk would scan the host’s plugin folder and find nothing.

Failing that, it falls back to an mpm on the PATH, then to the module under the interpreter running the plugin, and under python3. Every candidate is run before it is ranked, so an unusable one is skipped instead of being picked.

The menu answers that in place. Its last row, About, expands to three lines: the version this script advertises to its host, the release of the mpm it resolved, and the command that answered. The two are installed separately, so a bug report needs both: a plugin file copied into the host’s folder stays at the version it was copied at while mpm moves under it.

To see every candidate it considered, and not the winner alone, ask the plugin. It prints each one it found, in the order it ranked them, with the version it answered and the error it failed with:

$ python3 "$(mpm --bar-plugin-path)" --search-mpm

Python >= 3.9 required

The plugin requires Python 3.9 or newer, and runs on the interpreter macOS provides, without any extra dependency.

macOS ships no Python of its own: /usr/bin/python3 is a stub that runs the interpreter bundled with the Command Line Tools, and offers to install them when they are missing:

$ python3 --version
xcode-select: note: no developer tools were found at '/Applications/Xcode.app', requesting install. Choose an option in the dialog to download the command line developer tools.

The version that answers therefore tracks the Command Line Tools, not the macOS release:

macOS

Released

Security updates until[1]

Command Line Tools

python3[2]

26.x - Tahoe

2025-09-15

current

26

3.9.6

15.x - Sequoia

2024-09-16

current

16

3.9.6

14.x - Sonoma

2023-09-26

current

15

3.9.6

13.x - Ventura

2022-10-24

2025-09-15

14

3.9.6

12.x - Monterey

2021-10-25

2024-09-16

13

3.8.9

Every macOS still receiving security updates answers 3.9.6. Monterey is the only release below the requirement, and it stopped receiving updates in 2024.

Development workflow

Active development of the plugin is happening here, as a side-project of mpm itself.

Releases of the plugin are synchronized with the package. Both share the exact same version to simplify management. This explains why the plugin can appear to jump ahead a couple of major/minor versions while providing tiny or no changes at all.

A release is ready when both the package and the plugin reach a stable state.

If the plugin has been changed between releases, a copy of the plugin is pushed under the name meta_package_manager.7h.py, to the official Xbar plugin repository.

Release process

  1. Fork the official Xbar plugin repository.

  2. Fetch a local copy of the fork:

    $ git clone https://github.com/kdeldycke/xbar-plugins
    $ cd xbar-plugins
    
  3. Create a new branch and switch to it:

    $ git branch "meta-package-manager-v7.6.0"
    $ git checkout "meta-package-manager-v7.6.0"
    
  4. Replace existing copy of the plugin with the latest tagged version:

    $ wget https://raw.githubusercontent.com/kdeldycke/meta-package-manager/v7.6.0/meta_package_manager/bar_plugin.py
    $ mv ./bar_plugin.py ./Dev/meta_package_manager.7h.py
    $ chmod 755 ./Dev/meta_package_manager.7h.py
    
  5. Commit the new plugin:

    $ git add ./Dev/meta_package_manager.7h.py
    $ git commit -m "Upgrade to Meta Package Manager plugin v7.6.0"
    
  6. Push new branch:

    $ git push --set-upstream origin "meta-package-manager-v7.6.0"
    
  7. Create a pull-request in the original repository.

Changelog

  • 8.0.0.dev0 (unreleased)

    • Cap how wide a version renders in a menu line, so an over-long one no longer blanks the target version of its own row. Tune it with VAR_MAX_VERSION_WIDTH.

    • Name SwiftBar ahead of Xbar wherever the pair appears, the plugin page title included. SwiftBar is the maintained host of the two.

    • Render an error line at the same size as every other monospace row. Its own smaller size left a visible gap under the message, both hosts putting the surplus below the text rather than around it.

    • Separate a package name from its version diff by two spaces, in the fixed-width table and the variable-width rendering alike. A single space left bind9-dnsutils 1:9.20.18-1ubuntu2.1 1:9.20.24-1ubuntu0.1 reading as one run of text.

    • Give the plugin’s location section one tab per host. SwiftBar has no default plugin folder and must not be pointed at ~/Library/Application Support/SwiftBar/Plugins, which is its own data directory; the conventional ~/.swiftbar needs SwiftBar 2.1.1. Xbar’s folder is fixed.

    • Say where the macOS python3 comes from: /usr/bin/python3 is a stub running the interpreter of the Command Line Tools, which is what the version table is keyed by.

    • Publish a Desktop menus page above the two frontends, carrying what they share: what a click runs, where their settings stop and mpm’s configuration starts, the version-diff colors and the bootstrap pair. Both keep their own URL.

    • Breaking: rename the grouped-layout setting to VAR_GROUP_BY_MANAGER in the plugin and group-by-manager in the extension. Only Xbar ever opened a real sub-menu; SwiftBar folds an accordion and the extension expands inline, so the old name described one host’s rendering rather than the setting. Any configuration setting the old name reverts to the default.

    • Align the version arrows across every manager in the table rendering, so they line up down the whole menu instead of only within a section. Xbar’s grouped layout keeps its per-manager widths, each sub-menu being a panel of its own.

    • Split a version diff at the same point in both frontends. The GNOME menu left the separator introducing the diverging token in the dimmed prefix, where the bar plugin colors it: 5.0.0~beta1-0ubuntu7 highlighted 0~beta1-0ubuntu7 in one and .0~beta1-0ubuntu7 in the other, and 17 of 21 sampled pairs disagreed.

    • Stop coloring both versions in full when one is the other plus a whole new token. 14ubuntu614ubuntu6.1 highlights .1 alone, in place of every token including the ones that matched.

    • Publish the documentation at https://mpm.run. Every absolute link in readme.md, the benchmark tables, the packaging specs and both frontends’ metadata points at the new origin; the former kdeldycke.github.io URLs redirect to it.

    • Fix the plugin rendering a version error instead of its menu on every released SwiftBar. The minimum requirement was pinned on an unreleased 2.1.2 test build, while the fix it targets shipped in 2.1.0.

    • Quote the default values of the plugin variables, so SwiftBar 2.1.0 and above parses them and exposes them in its settings UI. Xbar still trims the quotes.

    • Declare the font variables as swiftbar.var and drop the swiftbar.environment block they were hidden in, so all four variables are editable from SwiftBar’s UI.

    • Fold each manager section into an inline accordion on SwiftBar 2.1.0 and above, expanding in place on click instead of opening a sub-menu, in the grouped layout.

    • Render each manager’s outdated count as a native SwiftBar badge on its section header, in place of the count spelled out in the label.

    • Add the VAR_HIDE_WHEN_UP_TO_DATE variable, removing the menu bar icon entirely while no package is outdated and no manager reported an error.

    • Stop the plugin’s virtualenv search at the user’s home folder, as its comment always claimed: it kept climbing to /Users and /, where a stray lockfile in a shared parent would have been taken for the plugin’s own project.

    • Add an About row to the menu, naming the plugin’s own version, the mpm release it resolved and the command that answered.

    • Offer uv tool install --upgrade meta-package-manager when no mpm is found, beside an entry opening the installation page for the systems uv does not answer for.

    • Stop a menu action from importing a source checkout it happens to be spawned from, instead of the installed mpm.

    • Give each frontend page a release history of its own, built from the changelog entries scoped to it.

    • Follow the symlink both hosts are installed through when looking for the mpm the plugin ships with, in place of the plugin folder it sits in.

    • Probe a virtualenv interpreter as itself, in place of the interpreter it was built from, which sees none of the virtualenv’s packages.

    • Drop the API reference sections the guide pages repeated at their foot; each module stays documented on its own API page.

  • 7.6.1 (2026-08-11)

    • Fix the uv launch candidate silently re-locking whatever project the plugin’s working directory landed in: it now runs frozen, pinned to the folder its lockfile was found in.

  • 7.6.0 (2026-08-10)

    • Menu actions run mpm itself instead of the manager’s native upgrade command, so manager selection, sudo policy, overrides and the cooldown all apply. A manager that cannot enforce an active cooldown is now skipped with a warning.

  • 7.4.1 (2026-07-28)

    • Recolor the version-diff suffixes to match the SwiftBar menu appearance (from OS_APPEARANCE): the adaptive system red and green lose contrast on recent macOS’s translucent menus, so a light menu darkens both and a dark menu brightens the green.

  • 7.4.0 (2026-07-25)

    • Fix a crash when probing an mpm invocation whose binary does not exist: the failed probe now reports the error instead of raising UnboundLocalError.

    • Version-diff colors now reach SwiftBar and Xbar: the highlighting introduced in 7.3.0 never rendered in the menu bar, because the plugin captures mpm outdated --plugin-output through a pipe, where the automatic color detection stripped every ANSI code. Plugin output now keeps its colors on any output stream, unless colorization is explicitly disabled (--color=never, NO_COLOR). ANSI codes captured inside relayed error messages are stripped instead, since error lines are marked ansi=false. The unchanged-version prefix also moves from bright-black to a theme-neutral xterm-256 mid-gray on menu lines only: SwiftBar maps bright-black to a fixed dark gray, near-invisible on dark-mode menus, while the mpm outdated table keeps the terminal-themed bright-black.

    • An outdated package whose manager provides no single-package upgrade command no longer crashes the rendering: its menu line simply carries no upgrade action.

  • 7.3.0 (2026-07-17)

    • The <xbar.version> header tracks the package version again: it fell out of the version-bump configuration during the bump-my-version migration and was stuck at 7.0.0.dev0.

    • Version differences are now highlighted in the outdated-package menu lines, with the same coloring as mpm outdated: common prefix gray, installed suffix red, latest suffix green. Package lines carry ansi=true so both SwiftBar and Xbar render the colors.

  • 7.1.0 (2026-07-07)

    • Cap each mpm call the plugin makes at 60 seconds via --timeout, so a wedged package manager fails the menubar refresh in about a minute instead of stalling it for several.

  • 6.5.0 (2026-05-25)

    • Swap default and alternate actions on package-upgrade entries: a regular click now opens a visible terminal, and holding Option runs the upgrade silently. Reverses the 3.3.0 behavior.

  • 5.21.0 (2025-05-29)

    • Bump minimal Python version to 3.9 to aligns it with macOS default.

    • Check minimal version of SwiftBar is 2.1.2.

    • Reduce the font size of error messages from 12 to 10 points.

  • 5.17.0 (2024-07-08)

    • Identify uv-based virtual envs to run mpm executable.

  • 5.16.0 (2024-05-24)

    • Dynamiccaly search for Python, virtual envs and mpm executable instead of relying on hard-coded PATH environment variable.

    • Replace --check-mpm parameter by --search-mpm with complete results reporting.

  • 5.15.0 (2024-02-25)

    • Keep original indention of Python traceback.

  • 5.14.1 (2024-01-16)

    • Always call mpm --version without color.

    • Increase robustness of mpm version parsing, whether its colored or not.

  • 5.10.0 (2022-12-19)

    • Disable --bar-plugin-path option if CLI not installed from sources.

    • Rename and move meta_package_manager.7h.py bar plugin script to eliminate dynamic module loading.

  • 5.6.0 (2022-09-26)

    • Check for minimal Python version.

    • Add a --check-mpm option to test the mpm binary search phase without running a full outdated package listing.

  • 5.5.1 (2022-07-11)

    • Silence all errors but critical ones on outdated invocation to prevent a failing manager to block rendering of the plugin output.

  • 5.3.0 (2022-06-25)

    • Fix rendering of package managers without outdated packages. Closes #631.

  • 5.0.0 (2022-04-25)

    • Add new DEFAULT_FONT and MONOSPACE_FONT variable.

    • Rename all reference of xbar to the generic bar-plugin label.

    • Improve search for Python and mpm executable.

    • Restructure the plugin ↔ mpm relationship to delegate all plugin layout and rendering logic to mpm.

    • Prevent leaks when modifying environment variables.

  • 4.13.1 (2022-04-17)

    • Fix location of mpm binary on Apple Silicon machines.

  • 4.13.0 (2022-04-16)

    • Add support for SwiftBar.

    • Add new TABLE_RENDERING option to plugin.

    • Improve alignment of labels in monospaced font rendering.

    • Tweak icons.

  • 4.11.0 (2022-04-03)

    • Fix log verbosity of the xbar plugin.

  • 4.4.0 (2021-09-27)

    • Let xbar plugin check minimal mpm version requirement.

  • 4.2.0 (2021-09-21)

    • Fix xbar plugin output format.

    • Rename VAR_SUBMENU_lAYOUT environment variable to VAR_SUBMENU_LAYOUT.

  • 4.1.0 (2021-05-01)

    • Add new Submenu layout boolean option in xbar plugin UI.

    • Rename XBAR_MPM_SUBMENU environment variable to VAR_SUBMENU_lAYOUT.

  • 4.0.0 (2021-04-27)

    • Upgrade BitBar plugin to new xbar format.

    • Drop xbar plugin requirement on Python 2.x and bump it up to Python 3.7.3.

    • Update references of BitBar to xbar.

    • Rename BITBAR_MPM_SUBMENU environment variable to XBAR_MPM_SUBMENU.

  • 3.4.0 (2020-08-18)

    • Force refresh of local package databases before fetching outdated ones.

  • 3.3.0 (2020-06-23)

    • Each entry in the drop-down menu can now be called into a terminal to track the execution by holding the Option key.

    • Fix rendering of upgrade CLI in Bitbar dialect.

  • 2.3.0 (2017-01-15)

    • Make it easier to change the font, size and color of text in BitBar plugin.

    • Move error icon in BitBar plugin to the front of manager name.

    • Move BitBar plugin documentation to dedicated page.

  • 2.2.0 (2016-12-25)

    • Add optional submenu rendering for BitBar plugin. Closes #23.

    • Move Upgrade all menu entry to the bottom of each section in BitBar plugin.

  • 2.1.1 (2016-12-17)

    • Do not render emoji in BitBar plugin menu entries.

    • Do not trim error messages rendered in BitBar plugin.

  • 2.1.0 (2016-12-14)

    • Adjust rendering of BitBar plugin errors.

    • Move BitBar plugin within the Python module.

    • Force LANG environment variable to en_US.UTF-8.

    • Share same code path for CLI execution between mpm and BitBar plugin.

  • 2.0.0 (2016-12-04)

    • Rewrite BitBar plugin based on mpm. Closes #13.

    • Render errors with a monospaced font in BitBar plugin.

  • 1.12.0 (2016-12-03)

    • Allow user to choose rendering of upgrade_cli field to either one-liner, fragments or BitBar format. Closes #14.

  • 1.8.0 (2016-08-22)

    • Copy all BitBar plugin code to Python module.