TL;DR:
- WezTerm is a free, open-source terminal emulator written in Rust with GPU-accelerated rendering, a built-in multiplexer (tabs, panes, workspaces), and configuration in Lua rather than TOML or JSON
- It’s cross-platform: macOS, Linux, and Windows, with the same config file working across all three — useful for developers who work across systems
- The Lua config is more expressive than most terminal configs: you can write conditionals, query environment variables, define keybind functions, and dynamically change behaviour based on context
Why Another Terminal?
The case for WezTerm comes down to three things: GPU rendering, the built-in multiplexer, and Lua config.
GPU rendering means text rendering happens on the GPU rather than the CPU. In practice, this matters when you’re scrolling large log outputs, running git log --graph, or working with tmux panes. The difference from a software-rendered terminal is most visible on high-DPI displays and when scrolling fast.
Built-in multiplexer means you get tabs, panes, and workspaces without tmux or screen. This is a choice, not a requirement — you can still run tmux inside WezTerm if you prefer. But for developers who don’t want to manage a tmux config, having pane splitting and tab management built in removes a layer.
Lua config means configuration is a real programming language. If you’ve spent time fighting the limits of TOML-based configs or JSON with no comments, having if/then, functions, and access to the WezTerm API is genuinely useful.
Installation
WezTerm is available through most package managers:
# macOS (Homebrew)
brew install --cask wezterm
# Ubuntu/Debian
curl -fsSL https://apt.fury.io/wez/gpg.key | sudo gpg --yes --dearmor -o /usr/share/keyrings/wezterm-fury.gpg
echo 'deb [signed-by=/usr/share/keyrings/wezterm-fury.gpg] https://apt.fury.io/wez/ * *' | sudo tee /etc/apt/sources.list.d/wezterm.list
sudo apt update && sudo apt install wezterm
# Windows (winget)
winget install wez.wezterm
Configuration goes in ~/.wezterm.lua (or ~/.config/wezterm/wezterm.lua). WezTerm reloads the config on save without restarting.
Basic Configuration
A minimal config:
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.color_scheme = 'Tokyo Night'
config.font = wezterm.font('JetBrains Mono', { weight = 'Medium' })
config.font_size = 14.0
config.window_background_opacity = 0.95
config.hide_tab_bar_if_only_one_tab = true
return config
wezterm.config_builder() is the recommended way to build config — it provides validation and helpful error messages if you typo a field name. return config at the end is required.
Key Bindings
WezTerm’s key binding system lets you bind to actions or Lua functions:
local act = wezterm.action
config.keys = {
-- Split pane horizontally
{ key = 'd', mods = 'SUPER', action = act.SplitHorizontal { domain = 'CurrentPaneDomain' } },
-- Split pane vertically
{ key = 'd', mods = 'SUPER|SHIFT', action = act.SplitVertical { domain = 'CurrentPaneDomain' } },
-- Close current pane
{ key = 'w', mods = 'SUPER', action = act.CloseCurrentPane { confirm = false } },
-- Navigate panes with vim-style keys
{ key = 'h', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Left' },
{ key = 'l', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Right' },
{ key = 'k', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Up' },
{ key = 'j', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Down' },
}
The key binding table is additive — you’re extending the defaults, not replacing them. To clear defaults and start from scratch, set config.disable_default_key_bindings = true.
Workspaces
Workspaces in WezTerm are like tmux sessions — independent sets of tabs and panes you can switch between. Useful for keeping project contexts separate:
-- Switch to a workspace (or create it if it doesn't exist)
{ key = 's', mods = 'SUPER', action = act.ShowLauncherArgs { flags = 'WORKSPACES' } }
The launcher shows available workspaces and lets you create new ones with a name. Your SSH sessions, local dev environment, and monitoring panes can live in separate workspaces and you switch between them with a keystroke.
Lua for Dynamic Config
This is where WezTerm earns its Lua approach. A common pattern: different font sizes on different machines:
local hostname = wezterm.hostname()
if hostname == 'macbook-pro.local' then
config.font_size = 14.0
else
config.font_size = 12.0
end
Or adjusting opacity based on time of day:
local hour = tonumber(wezterm.strftime('%H'))
if hour >= 20 or hour < 8 then
config.window_background_opacity = 0.9
else
config.window_background_opacity = 1.0
end
The wezterm.on event system lets you run Lua code in response to terminal events:
-- Change tab title to show the running process
wezterm.on('format-tab-title', function(tab, tabs, panes, config, hover, max_width)
local pane = tab.active_pane
local title = pane.title
if pane.foreground_process_name then
local process = pane.foreground_process_name:match("([^/\\]+)$")
title = process .. ': ' .. title
end
return title
end)
Status Bar
The right status bar is configurable via Lua:
wezterm.on('update-right-status', function(window, pane)
local date = wezterm.strftime('%a %d %b %H:%M')
local workspace = window:active_workspace()
window:set_right_status(wezterm.format {
{ Foreground = { Color = '#a9b1d6' } },
{ Text = workspace .. ' ' .. date .. ' ' },
})
end)
How It Compares
| Feature | WezTerm | Alacritty | iTerm2 |
|---|---|---|---|
| GPU rendering | Yes | Yes | No |
| Built-in multiplexer | Yes | No | Yes |
| Config language | Lua | TOML | GUI + XML |
| Cross-platform | Yes | Yes | macOS only |
| Ligature support | Yes | Optional | Yes |
| Image rendering | Yes | No | Yes |
| SSH multiplexer | Yes (native) | No | No |
| Price | Free/OSS | Free/OSS | Free/OSS |
Alacritty is faster to start but has no multiplexer and minimal config expressiveness. iTerm2 is macOS-only and config lives in a GUI rather than a file. WezTerm occupies the middle: more configurable than Alacritty, cross-platform unlike iTerm2, and with a built-in multiplexer that removes the need for tmux for most workflows.
Nerd Fonts and Symbols
WezTerm has built-in symbol fallback for nerd fonts. If you’re using a prompt that renders powerline symbols or dev icons (Starship, Oh My Posh), you don’t need to install a patched Nerd Font — you can use the regular JetBrains Mono or Fira Code and let WezTerm render the symbols from its built-in fallback:
config.font = wezterm.font_with_fallback {
'JetBrains Mono',
'JetBrainsMono Nerd Font',
'Symbols Nerd Font Mono',
}