⚙️ Python API / wrapper for tmux
See the codeDrive 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.
from_env().cmd(...) on any objectMaintenance-only backports (no new fixes):
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.*"
First, start a tmux session to connect to:
$ tmux new-session -s foo -n bar
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.
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_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()
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(%... ...)
>>> 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.window
Window(@... ...:..., Session($... ...))
>>> pane.window.session
Session($... ...)
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.
| 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)
| 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.
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
server and session fixtures per test, each on an isolated
tmux socket; derive windows and panes from sessionTestServer helper spins up multiple isolated tmux serversTopics: 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
Please use the following BibTeX template to cite libtmux in scientific discourse:
@misc{libtmux,
author = {Tony Narlock},
year = {2016},
note = {https://libtmux.git-pull.com},
title = {libtmux: python wrapper for tmux}
}
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.
Overall
Maintained
Code review
PyPI
Latest release
Releases this year
(top 24 of 25)
2,508 followers · starred May 2016
136 followers · starred Jul 2016
434 followers · starred Jan 2026
683 followers · starred Jul 2017
⚙️ Python API / wrapper for tmux
See the codeDrive 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.
from_env().cmd(...) on any objectMaintenance-only backports (no new fixes):
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.*"
First, start a tmux session to connect to:
$ tmux new-session -s foo -n bar
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.
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_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()
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(%... ...)
>>> 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.window
Window(@... ...:..., Session($... ...))
>>> pane.window.session
Session($... ...)
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.
| 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)
| 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.
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
server and session fixtures per test, each on an isolated
tmux socket; derive windows and panes from sessionTestServer helper spins up multiple isolated tmux serversTopics: 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
Please use the following BibTeX template to cite libtmux in scientific discourse:
@misc{libtmux,
author = {Tony Narlock},
year = {2016},
note = {https://libtmux.git-pull.com},
title = {libtmux: python wrapper for tmux}
}
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.
Overall
Maintained
Code review
PyPI
Latest release
Releases this year
(top 24 of 25)
2,508 followers · starred May 2016
136 followers · starred Jul 2016
434 followers · starred Jan 2026
683 followers · starred Jul 2017