Drive tmux from Python: typed, object-oriented control over servers, sessions, windows, and panes.
libtmux is a typed Python API over tmux, the terminal multiplexer. Stop shelling out and parsing tmux ls. Instead, interact with real Python objects: Server, Session, Window, and Pane. The same API powers tmuxp, so it stays battle-tested in real-world workflows.
- Typed, object-oriented control of tmux state
- Query and traverse live sessions, windows, and panes
- Locate yourself from inside a pane with
from_env() - Raw escape hatch via
.cmd(...)on any object - Works with multiple tmux sockets and servers
- Context managers for automatic cleanup
- pytest plugin for isolated tmux fixtures
- Proven in production via tmuxp and other tooling
- tmux: >= 3.2a
- Python: >= 3.10 (CPython and PyPy)
Maintenance-only backports (no new fixes):
Stable release:
$ pip install libtmuxWith pipx:
$ pipx install libtmuxWith uv / uvx:
$ uv add libtmux$ uvx --from "libtmux" pythonFrom the main branch (bleeding edge):
$ pip install 'git+https://github.com/tmux-python/libtmux.git'Tip: libtmux is pre-1.0. Pin a range in projects to avoid surprises:
requirements.txt:
libtmux==0.50.*pyproject.toml:
libtmux = "0.50.*"First, start a tmux session to connect to:
$ tmux new-session -s foo -n barUse ptpython, ipython, etc. for a nice REPL with autocompletions:
$ pip install --user ptpython$ ptpythonConnect to a live tmux session:
>>>importlibtmux>>>svr=libtmux.Server()
>>>svrServer(socket_path=/tmp/tmux-.../default)Tip: You can also use tmuxp's tmuxp shell to drop straight into your
current tmux server / session / window / pane.
Every object has a .cmd() escape hatch that honors socket name and path:
>>>server=Server(socket_name='libtmux_doctest')
>>>server.cmd('display-message', 'hello world')
<libtmux...>Create a new session:
>>>server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0]
'$...'>>>server.sessions
[Session($... ...), ...]Filter by attribute:
>>>server.sessions.filter(history_limit='2000')
[Session($... ...), ...]Direct lookup:
>>>server.sessions.get(session_id=session.session_id)
Session($... ...)Learn more about Workspace Setup
>>>session.rename_session('my-session')
Session($... my-session)Create new window in the background (don't switch to it):
>>>bg_window=session.new_window(attach=False, window_name="bg-work")
>>>bg_windowWindow(@... ...:bg-work, Session($... ...))
>>>session.windows.filter(window_name__startswith="bg")
[Window(@... ...:bg-work, Session($... ...))]
>>>session.windows.get(window_name__startswith="bg")
Window(@... ...:bg-work, Session($... ...))
>>>bg_window.kill()Learn more about Pane Interaction
>>>pane=window.split(attach=False)
>>>panePane(%... Window(@... ...:..., Session($... ...)))Type inside the pane (send keystrokes):
>>>pane.send_keys('echo hello')
>>>pane.send_keys('echo hey', enter=False)
>>>pane.enter()
Pane(%... ...)>>>pane.clear()
Pane(%... ...)
>>>pane.send_keys("echo 'hello world'", enter=True)
>>>pane.cmd('capture-pane', '-p').stdout# doctest: +SKIP
["$ echo 'hello world'", 'hello world', '$']Navigate from pane up to window to session:
>>>pane.windowWindow(@... ...:..., Session($... ...))
>>>pane.window.sessionSession($... ...)Learn more about Locating yourself
Code running inside a pane — a script in a split, a tmux hook, an agent — can ask where it is. tmux writes TMUX and TMUX_PANE into every pane it spawns, and Server, Session, Window, and Pane each read them back:
>>>socket_path=server.cmd(
... "display-message", "-p", "-t", session.session_id, "#{socket_path}"
... ).stdout[0]
>>>monkeypatch.setenv("TMUX", f"{socket_path},1,{session.session_id}")
>>>monkeypatch.setenv("TMUX_PANE", pane.pane_id)
>>>Pane.from_env()
Pane(%... ...)
>>>Session.from_env().session_name==session.session_nameTrueIn a real pane tmux has already set those two variables, so from_env() takes no arguments and there is nothing to arrange — this README is not running in a pane, so the example sets them first. Outside tmux there is no pane to return, and from_env() raises NotInsideTmux.
| libtmux object | tmux concept | Notes |
|---|---|---|
Server | tmux server / socket | Entry point; owns sessions |
Session | tmux session ($0, $1,...) | Owns windows |
Window | tmux window (@1, @2,...) | Owns panes |
Pane | tmux pane (%1, %2,...) | Where commands run |
Also available: Options and Hooks abstractions for tmux configuration.
Collections are live and queryable:
server=libtmux.Server()
session=server.sessions.get(session_name="demo")
api_windows=session.windows.filter(window_name__startswith="api")
pane=session.active_window.active_panepane.send_keys("echo 'hello from libtmux'", enter=True)| Tool | Layer | Typical use case |
|---|---|---|
| tmux | CLI / terminal multiplexer | Everyday terminal usage, manual control |
| libtmux | Python API over tmux | Programmatic control, automation, testing |
| tmuxp | App on top of libtmux | Declarative tmux workspaces from YAML / TOML |
Learn more about the pytest plugin
Writing a tool that interacts with tmux? Use our fixtures to keep your tests clean and isolated.
deftest_my_tmux_tool(session):
# session is a real tmux session in an isolated serverwindow=session.new_window(window_name="test")
pane=window.active_panepane.send_keys("echo 'hello from test'", enter=True)
assertwindow.window_name=="test"# Fixtures handle cleanup automatically- Fresh tmux server/session/window/pane fixtures per test
- Temporary HOME and tmux config fixtures keep indices stable
TestServerhelper spins up multiple isolated tmux servers
- Layouts are static and live entirely in tmux config files
- You do not need to introspect or control running tmux from other tools
- Python is unavailable where tmux is running
Topics:Traversal · Filtering · Pane Interaction · Workspace Setup · Automation Patterns · Context Managers · Options & Hooks
Reference:Docs · API · pytest plugin · Architecture · Changelog · Migration
Project:Issues · Coverage · Releases · License · Support
The Tao of tmux — deep-dive book on tmux fundamentals
Contributions are welcome. Please open an issue or PR if you find a bug or want to improve the API or docs. If libtmux helps you ship, consider sponsoring development via support.