⚙️ libtmux
Drive tmux from Python: typed, object-oriented control over servers, sessions, windows, and panes.
🐍 What is libtmux?
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.
✨ Features
- 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
Requirements & support
- tmux: >= 3.2a
- Python: >= 3.10 (CPython and PyPy)
Maintenance-only backports (no new fixes):
📦 Installation
Stable release:
$ pip install libtmux
With pipx:
$ pipx install libtmux
With uv / uvx:
$ uv add libtmux
$ uvx --from "libtmux" python
From 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.*"
🚀 Quickstart
Open a tmux session
First, start a tmux session to connect to:
$ tmux new-session -s foo -n bar
Pilot your tmux session via Python
Use ptpython, ipython, etc. for a nice REPL with autocompletions:
$ pip install --user ptpython
$ ptpython
Connect to a live tmux session:
>>> import libtmux
>>> svr = libtmux.Server()
>>> svr
Server(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.
Run any tmux command
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]
'$...'
List and filter sessions
>>> server.sessions
[Session($... ...), ...]
Filter by attribute:
>>> server.sessions.filter(history_limit='2000')
[Session($... ...), ...]
Direct lookup:
>>> server.sessions.get(session_id=session.session_id)
Session($... ...)
Control sessions and windows
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_window
Window(@... ...: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()
Split windows and send keys
Learn more about Pane Interaction
>>> pane = window.split(attach=False)
>>> pane
Pane(%... Window(@... ...:..., Session($... ...)))
Type inside the pane (send keystrokes):
>>> pane.send_keys('echo hello')
>>> pane.send_keys('echo hey', enter=False)
>>> pane.enter()
Pane(%... ...)
Capture pane output
>>> pane.clear()
Pane(%... ...)
>>> pane.send_keys("echo 'hello world'", enter=True)
>>> pane.cmd('capture-pane', '-p').stdout # doctest: +SKIP
["$ echo 'hello world'", 'hello world', '$']
Traverse the hierarchy
Navigate from pane up to window to session:
>>> pane.window
Window(@... ...:..., Session($... ...))
>>> pane.window.session
Session($... ...)
Know where you're running
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_name
True
In 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.
Core concepts
| 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_pane
pane.send_keys("echo 'hello from libtmux'", enter=True)
tmux vs libtmux vs tmuxp
| 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 |
Testing & fixtures
Learn more about the pytest plugin
Writing a tool that interacts with tmux? Use our fixtures to keep your tests clean and isolated.
def test_my_tmux_tool(session):
# session is a real tmux session in an isolated server
window = session.new_window(window_name="test")
pane = window.active_pane
pane.send_keys("echo 'hello from test'", enter=True)
assert window.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
When you might not need libtmux
- 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
Project links
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
Contributing & support
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.
Metadata
Release files for libtmux 0.62.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| libtmux-0.62.0.tar.gz | 571.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| libtmux-0.62.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 699.5 kB
Release files / libtmux-0.62.0.tar.gz
| Download URL | libtmux-0.62.0.tar.gz |
|---|---|
| Size | 571.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41e9e80602b2656fd119b13253b27bad46af5cfef32099be53cdd391e2936b61
|
|
BLAKE2b-256 checksum How to use checksums |
0cb80410c8487f7673926d141a5aaaf60133984a564b3c90eb4f3f99e92e7740
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / libtmux-0.62.0-py3-none-any.whl
| Download URL | libtmux-0.62.0-py3-none-any.whl |
|---|---|
| Size | 127.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
626aa6fae45e3a423e1acc81604efafa63b7262b1d67b2c7a8778b1ad691456b
|
|
BLAKE2b-256 checksum How to use checksums |
8796471ac01844ee157fe794446e17568e9bf219d323575c2acc7957a9a2d8c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency log