CLI tool
jstkdng/ueberzugpp avatar
jstkdng/ueberzugpp

Überzug++ draws images in a terminal, and replaces a project that died

Drop in replacement for ueberzug written in C++

1,324 stars49 forksC++GPL-3.0

At a glance

What is it?
Überzug++ is a C++ command line utility that displays images inside a terminal, using X11 or Wayland child windows, sixels, the kitty and iTerm2 graphics protocols, or chafa, and it exists because the original ueberzug project is defunct. Its command interface is two JSON verbs over stdin or a unix socket, which is why the applications that supported ueberzug keep working unchanged.
Who is it for?
Überzug++ suits a Linux or macOS user whose file manager or previewer needs to show images inside the terminal, and it suits a user of yazi, vifm, image.nvim, ytfzf or lf who wants image previews on Wayland where the original ueberzug never worked.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A drop-in replacement for a project that no longer exists

Überzug++ is a command line utility written in C++ which allows drawing images on terminals by using X11 or Wayland child windows, sixels, kitty and iTerm2 graphics. The reason it exists is stated plainly: it is intended to be a drop-in replacement for the now defunct ueberzug project.

That framing explains the interface. ueberzug was a small daemon that file managers and preview tools talked to, and a great deal of software learned to speak its protocol. Überzug++ keeps the protocol, so an application that worked with ueberzug should work out of the box with this project, and the readme lists the applications that support it: ytfzf, lobster, vifm, rnvimr, image.nvim, yazi and twitchez.

The audience is therefore split in two. There are the users of those file managers and preview tools, who get image previews back without changing anything, and there are the developers of new tools, who are pointed at the socket based interface because the older stdin approach does not work in every combination of terminal, multiplexer and application.

The licence is GPL-3.0, and the code is C++20 with GCC 10.1 as the minimum compiler. The last push was on 2026-09-20 and the repository is not archived, so the project is being worked on.

The advantages the project lists over w3mimgdisplay and over ueberzug itself are the substantive part of the case. Wayland support covers sway, hyprland, niri and wayfire, which is where the original project was blind. macOS is supported. There are no race conditions, because a new window is created to display each image rather than a shared surface being repainted. Expose events are processed, so images are redrawn when you switch workspaces, which is the bug that makes an image preview look broken in a compositor. tmux works on X11, sway and hyprland. Terminals without the WINDOWID environment variable are supported. Characters are used as the position and size unit, so the image lines up with the terminal grid. And there is no memory leak, because smart pointers are used throughout.

Six backends, and the output flag that picks one

The output backends are the core of the design, because a terminal image is not one thing.

On X11 and Wayland the image is drawn in a child window positioned over the terminal, which gives the best result because the terminal never has to understand the pixels. Sixels is a terminal graphics protocol that encodes the image in the character stream, which is the classic approach for terminals that support it. The kitty graphics protocol and the iTerm2 protocol are the modern equivalents, each with its own way of transferring an image to the terminal emulator. And chafa is the fallback that converts an image into coloured block characters, which works in any terminal at the cost of being an approximation rather than a picture.

The choice is made with the output option, which the help text shows as accepting x11, wayland, sixel, kitty, iterm2 and chafa:

bash
  -o,--output TEXT:{x11,wayland,sixel,kitty,iterm2,chafa}
                              Image output method

The configuration file makes the same choice permanent. The file lives at $XDG_CONFIG_HOME/ueberzugpp/config.json, or at ~/.config/ueberzugpp/config.json when that variable is not set, and the documented format is:

json
{
  "layer": {
    "silent": true,
    "use-escape-codes": false,
    "no-stdin": false,
    "output": "sixel"
  }
}

Application flags take precedence over the configuration file, and the readme singles out the output variable as the most useful one, since it lets you force a particular method regardless of what the application requests.

Two other options in the help text are worth knowing about even though they are not about output. The no-opencv option says to use Libvips instead of OpenCV, and no-cache disables the caching of resized images. The cache matters for responsiveness, because resizing an image is the expensive part of drawing it, and the readme lists fast image downscaling through OpenCV and OpenCL plus caching of resized images among the project's features.

Two JSON verbs, and two ways to send them

The command protocol is tiny, and understanding it is enough to write a client.

To show an image, you send an object with an add action:

json
{"action":"add","identifier":"preview","max_height":0,"max_width":0,"path":"/path/image.ext","x":0,"y":0}

And to remove it, an object with a remove action and the same identifier:

json
{"action":"remove","identifier":"preview"}

The numeric values are the COLUMNS and LINES of the terminal window, and inside tmux they are relative to the size of the panel, which is why the same client can work in and out of a multiplexer. The identifier is what pairs the add with the remove, so a preview that is replaced is tracked by name.

There are two ways to deliver those objects. By default commands are sent over stdin, which is enough in some cases. The problem, as the readme describes it, is that in some terminal and application combinations, for example ranger with wezterm and zellij, using stdin to send commands does not work properly or the tool can fail to start altogether. In those cases the commands go through a unix socket, which by default the tool listens on at ${UEBERZUGPP_TMPDIR}/ueberzugpp-${PID}.socket. New software is encouraged to use sockets, because they cover more cases.

The temporary directory itself is configurable, and it holds the logs and the sockets:

sh
export UEBERZUGPP_TMPDIR="${TMPDIR}/ueberzugpp"

By default it is the system temporary directory. Setting it explicitly is the kind of thing that matters in a container or a locked down system where a shared temporary directory is not writable or not private.

The two commands the binary provides are layer, which is what you send commands to, and tmux, which is used internally. The layer help text also shows the two flags that no longer do anything, the parser and the loader options, marked as unused and present only for backwards compatibility, and that is the clearest sign of where this project sits relative to the one it replaces.

The file managers that drive it, and the scripts they use

The application list is where the value of the drop-in compatibility becomes concrete.

The listed tools are ytfzf, lobster, vifm, rnvimr, image.nvim, yazi and twitchez.

For the applications that need help wiring it up, the repository carries integration scripts. There is an lf script named lfub and an fzf script named fzfub, and they are the pattern to copy if you are adding support to something else.

What the compatibility buys is worth spelling out. Because the protocol is unchanged, an application written against ueberzug keeps working, which means the work of supporting terminal image previews in a file manager is not repeated for every new tool. That is the single most valuable property of this project, and it is why the project can be small and still be used by a range of applications.

The cost of the compatibility is the two dead flags. The parser and loader options are accepted and ignored, so an application that passes them will not break, but it also cannot rely on them doing anything, and any behaviour they used to control is now the tool's own default.

Building it, and the flags that decide what you get

Building from source is a CMake project, and the build flags are the interesting part because the defaults are not what most Linux users want.

The required dependencies are cmake 3.22 or newer, libvips, libsixel, chafa 1.6 or newer, openssl and tbb. On Ubuntu and Debian the install line is:

sh
apt-get install libssl-dev libvips-dev libsixel-dev libchafa-dev libtbb-dev

A set of dependencies is downloaded and included in the binary if it is not already installed: nlohmann-json, cli11, spdlog, fmt and range-v3. That vendoring is what makes the packaged builds self contained.

The optional dependencies are OpenCV, xcb-util-image, turbo-base64, Wayland with libwayland, wayland-protocols and extra-cmake-modules, and they are controlled by flags. The four documented flags and their defaults are ENABLE_OPENCV on by default, ENABLE_X11 on by default, ENABLE_TURBOBASE64 off by default, and ENABLE_WAYLAND off by default.

That last default is the one to notice, because Wayland support is a headline feature of the project and it is not compiled in unless you ask for it. The build sequence is the same in each case:

sh
git clone https://github.com/jstkdng/ueberzugpp.git
cd ueberzugpp
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_X11=OFF -DENABLE_OPENCV=OFF ..
cmake --build .

where the flags in that example disable X11 and OpenCV, and a second variant enables Turbo-Base64 with -DENABLE_TURBOBASE64=ON. The result is a binary in the build directory, and cmake --install build puts it in the default installation path if you want that.

For most people none of this is necessary, because there is a repology badge for the project and packages for x86_64, aarch64 and ppc64le in an openSUSE hosted repository, plus a Homebrew tap for macOS and Linux with brew install jstkdng/programs/ueberzugpp, and a debian/ directory in the repository for the distribution packages.

Release cadence, packaging and the limits to know about

The project is packaged widely and released regularly. The recent tags are v2.9.8 on 2025-10-28, v2.9.9 and v2.9.10 both on 2026-05-18, and the last push was on 2026-09-20 with the repository not archived. A patch version number in the high double digits with a release every few months is a healthy cadence for a tool that several applications depend on.

The packaging is the part that makes it easy to adopt, and it is unusually broad for a project of this size. Repology tracks it, the openSUSE hosted repository carries binaries for x86_64, aarch64 and ppc64le, Homebrew has it for macOS and Linux, and the repository holds a debian/ directory for distribution packaging. The architecture list is worth noting, because aarch64 and ppc64le coverage means the tool is packaged for more than the obvious desktops.

The limitations are the ones already described, and they are worth restating as a list. The Wayland build is off by default, so a package built without it will not do the thing most people on sway or hyprland want. The stdin interface fails in some terminal and application combinations and the socket interface is the answer, which means a new integration should use the socket from the start. The parser and loader flags are accepted and ignored. And the chafa output, while a genuine fallback, produces a character rendering rather than an image, so a user whose terminal has no graphics support gets something different from what they asked for.

One more thing the readme is candid about: the memory management, with no memory leak through the use of smart pointers, and the support for many image formats through OpenCV and libvips, with GIF and animated WebP supported on X11, Sixel, sway and hyprland. Those are the details that decide whether a preview looks right, and they are the parts of the project most likely to have been fixed by the recent patch releases.

Editorial conclusion

Überzug++ suits a Linux or macOS user whose file manager or previewer needs to show images inside the terminal, and it suits a user of yazi, vifm, image.nvim, ytfzf or lf who wants image previews on Wayland where the original ueberzug never worked. It is the wrong choice if your terminal has no graphics capability at all, because the fallback is chafa rendering rather than a real image, and a poor match if you need the tool to be a drop-in for a specific application that uses the removed parser or loader flags, which are marked unused and kept only for backwards compatibility. Before installing, check the output method your terminal supports, and if you build from source, turn on ENABLE_WAYLAND explicitly, since it is off by default.

Frequently asked questions

What is Überzug++ and why does it exist?

Überzug++ is a C++ command line utility that draws images on terminals using X11 or Wayland child windows, sixels, kitty and iTerm2, and it is intended as a drop-in replacement for the now defunct ueberzug project, so applications that supported ueberzug keep working.

How do I tell Überzug++ which output method to use?

Use the output option of the layer command, which accepts x11, wayland, sixel, kitty, iterm2 and chafa, or set the output value in the layer section of the configuration file at $XDG_CONFIG_HOME/ueberzugpp/config.json. Application flags take precedence over the configuration file.

How do I display an image with Überzug++?

Send a JSON object with an add action containing an identifier, the image path, the x and y position and the max_height and max_width in terminal columns and lines, and send a remove action with the same identifier to take it away again. Commands go over stdin by default, or over a unix socket in ${UEBERZUGPP_TMPDIR}/ueberzugpp-${PID}.socket.

Which applications support Überzug++?

The project lists ytfzf, lobster, vifm, rnvimr, image.nvim, yazi and twitchez, and it ships integration scripts for lf and fzf. New integrations are pointed at the unix socket rather than stdin, because stdin does not work in every terminal and application combination.

Official sources

  1. Issues
  2. jstkdng/ueberzugpp on GitHub
  3. License: GPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jstkdng-ueberzugpp.svg)](https://hysenlabs.com/projects/jstkdng-ueberzugpp)

Was this useful?