Build native desktop applications with Phoenix and Elixir.
ExTauri wraps Tauri to enable Phoenix LiveView applications to run as native desktop apps on macOS, Windows, and Linux.
- π Phoenix LiveView as Desktop Apps - Turn your Phoenix app into a native desktop application
- π¦ Single Binary Distribution - Uses Burrito to bundle everything into one executable
- π Hot Reload in Dev Mode - Full Phoenix development experience with live reload
- π― Graceful Shutdown - Heartbeat-based mechanism ensures clean shutdown on CMD+Q, crashes, or force-quit
- π Cross-Platform - Build for macOS, Windows, and Linux
- Rust
- Zig 0.10.0
- Elixir 1.14+
- Add ExTauri to your Phoenix project:
# mix.exs
def deps do
[
{:ex_tauri, git: "https://github.com/filipecabaco/ex_tauri.git"}
]
end- Configure ExTauri:
# config/config.exs
config :ex_tauri,
version: "2.5.1",
app_name: "My Desktop App",
host: "localhost",
port: 4000- Add Burrito release:
# mix.exs
def project do
[
# ... existing config
releases: releases()
]
end
defp releases do
[
desktop: [
steps: [:assemble, &Burrito.wrap/1],
burrito: [
targets: [
"aarch64-apple-darwin": [os: :darwin, cpu: :aarch64]
]
]
]
]
end- Add required applications:
# mix.exs
def application do
[
mod: {MyApp.Application, []},
extra_applications: [:logger, :runtime_tools, :inets]
]
end- Add ExTauri.ShutdownManager to your supervision tree:
# lib/my_app/application.ex
def start(_type, _args) do
children = [
MyApp.Repo,
{Phoenix.PubSub, name: MyApp.PubSub},
MyAppWeb.Endpoint,
ExTauri.ShutdownManager # Add this at the bottom of the children list
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end- Install Tauri:
mix deps.get
mix ex_tauri.installDevelopment (with hot reload):
mix ex_tauri.devBuild for distribution:
mix ex_tauri.buildYour app bundle will be at src-tauri/target/release/bundle/macos/YourApp.app (macOS) or equivalent for your platform.
ExTauri provides dedicated Mix tasks for common operations:
mix ex_tauri.install- Install and configure Tauri in your projectmix ex_tauri.dev- Run in development mode with hot-reloadmix ex_tauri.build- Build for production and create distributable packagesmix ex_tauri.info- Show information about your Tauri project and environmentmix ex_tauri.icon- Generate application icons from a source imagemix ex_tauri.signer- Manage code signing for application updates
Each task provides detailed help and options:
mix help ex_tauri.dev
mix help ex_tauri.build
# etc.ExTauri uses a robust Unix domain socket heartbeat mechanism to ensure the Phoenix sidecar shuts down gracefully when the desktop app exits:
- Elixir creates a Unix domain socket at
/tmp/tauri_heartbeat_<app_name>.sock - Rust connects and sends a byte every 100ms
- Elixir monitors heartbeats and checks every 100ms
- If no heartbeat for 300ms (3 missed beats), graceful shutdown is initiated
- Phoenix closes database connections, flushes logs, and exits cleanly
Zero HTTP overhead - Uses native Unix sockets (stdlib only, no dependencies!)
The socket path is unique per application (based on :app_name config) to prevent collisions when running multiple ExTauri apps simultaneously.
This works even when:
- The app is force-quit (CMD+Q on macOS)
- The app crashes unexpectedly
- The process is killed without cleanup
βββββββββββββββββββ
β Tauri Window β β Native UI (Rust)
β (WebView) β
ββββββββββ¬βββββββββ
β HTTP (for UI)
β Unix Socket (for heartbeat)
β
βββββββββββββββββββ
β Phoenix Server β β Your Elixir App
β (Sidecar) β (Burrito-wrapped)
β β /tmp/tauri_heartbeat_<app>.sock
βββββββββββββββββββ
config :ex_tauri,
version: "2.5.1", # Tauri version
app_name: "My App", # Application name
host: "localhost", # Phoenix host
port: 4000, # Phoenix port
window_title: "My Window", # Window title (defaults to app_name)
fullscreen: false, # Start in fullscreen
width: 800, # Window width
height: 600, # Window height
resize: true # Allow window resizeFor desktop apps, configure your database in config/runtime.exs:
database_path =
System.get_env("DATABASE_PATH") ||
Path.join([System.user_home!(), ".my_app", "my_app.db"])
File.mkdir_p!(Path.dirname(database_path))
config :my_app, MyApp.Repo,
database: database_path,
pool_size: 5Remove or comment out cache_static_manifest in config/prod.exs:
# Not needed for desktop apps:
# config :my_app, MyAppWeb.Endpoint,
# cache_static_manifest: "priv/static/cache_manifest.json"When building DMGs on macOS, you may encounter an AppleScript permission error:
execution error: Not authorised to send Apple events to Finder. (-1743)
This error prevents the DMG from being created - the creation script uses AppleScript to configure the DMG appearance (backgrounds, icon positions), but requires Finder automation permissions.
Solution: Grant Automation Permissions
- Open System Settings β Privacy & Security β Automation
- Find your development environment (Terminal, iTerm2, VS Code, etc.)
- Enable Finder access
After granting permissions, build normally:
cd example
mix ex_tauri.buildCheck the example directory for a complete working Phoenix desktop application with:
- SQLite database (Ecto + Exqlite)
- Phoenix LiveView
- Tailwind CSS
- Notes CRUD interface
- Tauri App - For the amazing framework and support
- Burrito by Digit/Doawoo - For enabling single-binary Elixir apps
- phx_new_desktop by Kevin Pan/Feng19 - For inspiration
- Phoenix Tailwind - For the package installation approach
MIT
