# desktop picks a wx, JSON or browser backend for you, and NO_WX is the escape hatch

> The desktop library gives a Phoenix LiveView application a native window on Windows, macOS and Linux, a JSON bridge on iOS and Android, and a headless fallback for CI. The routing happens through Desktop.Platform, and the README is unusually explicit about the automatic selection order, which is the part worth reading before you debug a window that will not open.

**elixir-desktop/desktop** — Building Local-First apps for Windows, MacOS, Linux, iOS and Android using Phoenix LiveView & Elixir!

- Repository: https://github.com/elixir-desktop/desktop
- Website: https://hexdocs.pm/desktop/
- Stars: 1,814 · Forks: 65
- Language: Elixir
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/elixir-desktop-desktop

## One behaviour, three very different implementations

The library's central abstraction is Desktop.Platform, and every window, webview, menu and notification call is routed through it to one of three backends. That indirection is the design, not an implementation detail, because it is what lets the same LiveView code run in a native window, in a phone host app, and in a headless test run.

`Desktop.Backend.Wx` gives native windows on Windows, macOS and Linux through OTP's `:wx` and wxWidgets. `Desktop.Backend.Json` serves Android and iOS by talking JSON over a TCP bridge to a native host app. `Desktop.Backend.Browser` is the escape hatch for headless CI, servers without wx, or local development without a GUI.

Automatic selection is the default and is spelled out as an ordered list. A mobile target first, detected either by the compile-time flag `config:desktop,:mobile_target, true` or by `Desktop.OS.mobile?/0` at runtime, selects Json. Failing that, if `NO_WX` is set or `:wx` is not available, it selects Browser. Otherwise it selects Wx.

## Forcing a backend, or writing your own

When automatic selection guesses wrong, the override is a single config line. The four accepted values map directly onto the backends:

```elixir
config :desktop, :backend, :wx      # force wxWidgets (desktop)
config :desktop, :backend, :json   # force JSON bridge (e.g. mobile host)
config :desktop, :backend, :browser # force OS-browser fallback
config :desktop, :backend, :auto   # automatic (default)
```

There is also a fourth option that is not a preset: a custom backend module. Anything implementing the `Desktop.Platform.*` behaviour callbacks can be placed in the same config slot:

```elixir
config :desktop, :backend, MyApp.DesktopBackend
```

That matters more than it first appears. It means the window layer is not locked to wxWidgets, so an application that needs a native surface wx cannot express is not stuck.

Two environment variables interact with all of this. `NO_WX=1` selects the Browser backend when the mode is `:auto`, which is the flag to set on a build machine. `BRIDGE_PORT` is the TCP port for the Json backend's native host, and its default of `0` is described as an in-process mock used for tests, which is a useful detail: it means the mobile transport can be exercised without a phone attached.

The repository also ships desktop_wx_stub.exs at the root, which is consistent with a design where a wx stand-in exists for environments where the real thing cannot load.

## Getting the toolchain right before anything else

The README's first instruction is about versions, not code. The repository's .tool-versions pins Erlang and Elixir for contributors, and both mise and asdf understand that file. After activating the toolchain, there is a command to confirm your versions actually match:

```bash
mix desktop.check_toolchain
```

It checks the running OTP major version and the Elixir version against what .tool-versions declares. This exists because version mismatches in this stack produce errors far from their cause, and a single command moves that diagnosis to the front.

For actual building, the README points at two guides rather than inlining steps: a Getting your Environment Ready guide and a Your first Desktop App guide, both under guides/. There is a separate FAQ guide covering the mobile bridge setup, headless testing and capability details.

A working example lives in a separate repository, desktop-example-app, which is the fastest way to see a finished application rather than reconstructing one from documentation. The README shows a macOS build of that example as a to-do application.

There is no Hex installation snippet in the README itself; the package is published on Hex as desktop, and the badge links to the hex package page and the docs at hexdocs.pm/desktop.

## The roadmap tells you what you will be doing yourself

The status list is short and honest, with three items marked done and the rest unchecked.

Done: running on Windows, macOS and Linux; running on iOS and Android; and packaging applications for iOS and Android, with the example-app repository covering the mobile side.

Not done: packaging for Windows, macOS and Linux into installers; updating the example app to ship built installers through GitHub Actions; using a repository of pre-compiled Erlang binaries per platform so that one `mix desktop.deploy` call produces an installer for all platforms; integrating code signing for Windows and macOS; and building a GitHub Actions based auto-update flow.

Read that list as a deployment plan. You can run and you can package for mobile, but for desktop you are currently assembling installers, signing and update delivery yourself.

There are no GitHub releases published for this repository, so there is no tagged version to pin from; the package is consumed from Hex instead.

## Where this library is the wrong choice

Three situations argue against it, and all three come from the same underlying fact: the window layer is wxWidgets on desktop.

First, if your target environment cannot run wxWidgets at all, you are pushed onto the Browser backend, which means the application is a LiveView page in an OS browser window rather than a native one. That is a different product, and the README positions it as a fallback for CI and servers rather than a target.

Second, if you need a native surface with behaviour wx cannot express, you are writing a backend module yourself. At that point the library is a behaviour definition you are implementing, and you should weigh whether that is cheaper than writing the window layer directly.

Third, if you need signed installers and automatic updates today. Those roadmap items are open, and they are the difference between a program you run and a program you can ship to users.

## Conclusion

desktop fits an Elixir team that wants to ship a LiveView application as a desktop program and to keep one codebase across desktop and mobile, and that is willing to own the packaging step themselves. It does not yet handle installers for Windows, macOS and Linux, since that item is still unchecked on the README roadmap along with code signing and auto-update. Verify first that your OTP major and Elixir version match .tool-versions by running mix desktop.check_toolchain, and read guides/faq.md for the mobile bridge setup before planning an iOS or Android build.

## FAQ

### How do I choose a backend in the Elixir desktop library?

The default is `config :desktop, :backend, :auto`, which selects Json on a mobile target, Browser when NO_WX is set or :wx is unavailable, and Wx otherwise. You can force `:wx`, `:json`, `:browser`, or your own module implementing the Desktop.Platform behaviour.

### How do I run the desktop library in CI without a display?

Set NO_WX=1 so that automatic selection picks the Browser backend, or set the backend explicitly to :browser. That backend exists for headless CI, servers without wx and local development without a GUI.

### What does BRIDGE_PORT do?

It is the TCP port for the Json backend's native host app, used on Android and iOS. Its default of 0 means an in-process mock, which is what makes the mobile transport testable without a device.

### Can I ship a desktop installer with the desktop library today?

Not yet. Running on Windows, macOS and Linux is marked done, but packaging desktop installers, built example installers through GitHub Actions, pre-compiled Erlang binaries, code signing and auto-update are all still unchecked roadmap items.

### How do I check my Erlang and Elixir versions match?

The repository's .tool-versions pins both, and mise or asdf can read it. After activating the toolchain, run `mix desktop.check_toolchain` to confirm the running OTP major and Elixir version match the file.

### Where is a working example of the desktop library?

In a separate repository, desktop-example-app, which the README presents as the starting point. Packaging for iOS and Android is covered there, and the README shows a macOS build of its to-do example.

## Sources

- [elixir-desktop/desktop on GitHub](https://github.com/elixir-desktop/desktop)
- [Issues](https://github.com/elixir-desktop/desktop/issues)
- [License: MIT](https://github.com/elixir-desktop/desktop/blob/main/LICENSE)
- [Project website](https://hexdocs.pm/desktop/)
- [README](https://github.com/elixir-desktop/desktop/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/elixir-desktop-desktop
