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

FeatureWezTermAlacrittyiTerm2
GPU renderingYesYesNo
Built-in multiplexerYesNoYes
Config languageLuaTOMLGUI + XML
Cross-platformYesYesmacOS only
Ligature supportYesOptionalYes
Image renderingYesNoYes
SSH multiplexerYes (native)NoNo
PriceFree/OSSFree/OSSFree/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',
}

References