# PyLabRobot puts one Python interface in front of every bench instrument

> PyLabRobot is a pure Python, hardware agnostic SDK for lab automation, covering liquid handlers, plate readers, centrifuges, pumps, scales, heater shakers, fans and thermocyclers, with each vendor reached through a backend. The pitch is that a protocol written once runs on a Hamilton STAR, an Opentrons OT-2 or a Tecan Freedom EVO without changes.

**PyLabRobot/pylabrobot** — interactive & hardware agnostic SDK for lab automation

- Repository: https://github.com/PyLabRobot/pylabrobot
- Website: https://docs.pylabrobot.org
- Stars: 616 · Forks: 222
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/pylabrobot-pylabrobot

## One interface, several vendors, and the same protocol

PyLabRobot is a hardware agnostic, pure Python library for liquid handling robots, plate readers, pumps, scales, heater shakers and other lab automation equipment. The argument in the README is stated as advantages over proprietary software, and the central one is universality: the same interface reaches any supported liquid handling robot, plate reader, pump, scale or heater shaker. The clearest demonstration is the same protocol on three machines. On a Hamilton STAR, with firmware, moving 100 microlitres from well A1 to A2 looks like this.

```python
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import STARBackend
from pylabrobot.resources import Deck

deck = Deck.load_from_json_file("hamilton-layout.json")
lh = LiquidHandler(backend=STARBackend(), deck=deck)
await lh.setup()

await lh.pick_up_tips(lh.deck.get_resource("tip_rack")["A1"])
await lh.aspirate(lh.deck.get_resource("plate")["A1"], vols=100)
await lh.dispense(lh.deck.get_resource("plate")["A2"], vols=100)
await lh.return_tips()
```

The Opentrons OT-2 version differs in two lines, the backend class and the layout file, with the host address supplied to the backend.

```python
from pylabrobot.liquid_handling.backends import OpentronsOT2Backend
deck = Deck.load_from_json_file("opentrons-layout.json")
lh = LiquidHandler(backend=OpentronsOT2Backend(host="x.x.x.x"), deck=deck)
```

And the Tecan Freedom EVO again swaps the backend and the layout.

```python
from pylabrobot.liquid_handling.backends import EVOBackend
deck = Deck.load_from_json_file("tecan-layout.json")
lh = LiquidHandler(backend=EVOBackend(), deck=deck)
```

The current liquid handling support covers Hamilton STAR, Hamilton Vantage, Tecan Freedom EVO and Opentrons OT-2, with more promised. Note the deck: it is loaded from a JSON file describing the labware layout, so the physical arrangement of the bench is data rather than code, and each vendor's layout lives in its own file.

## Atomic commands in a notebook, which is the iteration story

The claimed development speed comes from how protocols are written. The README says PyLabRobot enables rapid development of protocols using atomic commands run interactively in Jupyter notebooks or the Python REPL, and that this decreases iteration time from minutes to seconds. The `await lh.aspirate(...)` in the example above is one such atomic command. Run it in a notebook and you get a robot doing one move, and you see the result before you write the next line. The other half of the argument is control: with Python you can write Turing-complete protocols that include feedback loops, which the README contrasts with a fixed GUI workflow. The library is built on modern Python 3.10 or newer features and async/await syntax throughout, so every device operation is awaited, and a protocol is effectively an async function you can compose, loop over and branch on. There is also a browser-based Visualizer that shows the state of the deck during a run and can be used to develop and test protocols without a physical robot, which removes the biggest obstacle to iterating on a protocol you cannot yet afford to run.

## Beyond the liquid handler: readers, centrifuges, pumps, scales

The liquid handler is the flagship, but the SDK covers the rest of the bench, and each device class follows the same shape: a device class, a backend, a `setup` call, then awaited operations. A plate reader is constructed with a backend, a name and its dimensions, and read for luminescence.

```python
from pylabrobot.plate_reading import PlateReader, CLARIOstarBackend

pr = PlateReader(name="plate reader", backend=CLARIOstarBackend(), size_x=1, size_y=1, size_z=1)
await pr.setup()

# Use in combination with a liquid handler
lh.assign_child_resource(pr, location=Coordinate(x, y, z))
lh.move_plate(lh.deck.get_resource("plate"), pr)

data = await pr.read_luminescence()
```

The `assign_child_resource` and `move_plate` calls are how a plate gets from the liquid handler's deck onto the reader, which is the point of having a shared resource model rather than per-device scripts. A centrifuge is called with a force and a duration, and there is a separate factory for the HighRes Biosolutions MicroSpin.

```python
from pylabrobot.centrifuge import Centrifuge, VSpinBackend

vspin_backend = VSpinBackend(device_id="YOUR_FTDI_ID_HERE")
cf = Centrifuge(name="centrifuge", backend=vspin_backend, size_x=1, size_y=1, size_z=1)
await cf.setup()

await cf.spin(g=800, duration=60)
```

Pumps take a speed and a duration, scales are read through a serial driver, and heater shakers take a temperature. The heater shaker example shows the model carrying its own dimensions, so a device that occupies no deck space is declared with zeroes.

```python
from pylabrobot.heating_shaking import HeaterShaker, InhecoThermoShakeBackend

backend = InhecoThermoShakeBackend()
hs = HeaterShaker(backend=backend, name="HeaterShaker", size_x=0, size_y=0, size_z=0)
await hs.setup()
await hs.set_temperature(37)
```

## The extras decide what hardware you can actually reach

The packaging is where the practical constraints live. The core install depends only on `typing_extensions` and `websockets`, and everything that needs a physical transport is an optional extra. `serial` pulls in pyserial, `usb` pulls in pyusb and libusb-package, `ftdi` pulls in pylibftdi and pyusb, and `hid` pulls in the hid package. That is a reasonable design, since a robot on Ethernet needs none of them, but it means the install command is a decision rather than a one-liner. There is a `btx` extra for Bluetooth, `modbus` for pymodbus, `opentrons` for the OT-2 HTTP client with a pinned `opentrons-http-api-client==0.2.1` and zeroconf, `sila` for the SiLA standard over gRPC, and `rpc` for a gRPC and protobuf service. There is also an `all` extra that pulls most of them together, and a `test` extra with pytest. The version pins are worth reading, because the Opentrons client is pinned to an exact version and the modbus range is bounded below 3.7.0, which tells you the maintainers are working against specific protocol versions rather than latest releases. The project requires Python 3.10 or newer, and the command line entry point is `plr`, mapped to `pylabrobot.cli:main`.

## Building the docs, the tests and the RPC layer

The Makefile shows the working practices of the project. Documentation is Sphinx, with `sphinx-build -b html docs docs/build/ -j 16 -W`, and the `-W` flag turns warnings into errors, which is a strict setting for a documentation set that is this large. There is a faster variant that skips the API pages with `-t no-api`, a dummy build for checking, and a link checker target. Linting is ruff, and there is a separate target for the 3D viewer's JavaScript and CSS using Biome, which needs the `biome` binary. Tests run under pytest, type checking runs mypy against the `pylabrobot` package, and there is an `mypy.ini` at the top level. The `generate-rpc` target compiles the protobuf definitions for the device, jobs and images services, plus the Celigo service and storage protos, using grpc_tools.protoc with Python and mypy output. That last detail is significant: it means the RPC surface is defined in proto files and regenerated rather than hand-written, so the remote control API and the local Python API cannot drift apart. The repository also carries a `CITATION.cff`, a `CHANGELOG.md` and a `CONTRIBUTING.md`, and the paper describing the project was published in Device.

## What is missing, and when the abstraction leaks

Three limits are visible in what has been published. The first is hardware coverage. The liquid handling list is four robots with the promise of more, and the rest of the device classes each name a handful of backends, so a specific instrument that is not on the list is simply not reachable, and the README points at a supported machines page for the details. The second is that the abstraction is a Python library, not a scheduler. A protocol runs until the code says otherwise, so anything you would expect a lab management system to handle, queues, scheduling across instruments, run history, failure recovery, is your problem, and the feedback-loop flexibility the README celebrates cuts both ways. The third is the transport reality. An instrument reached over serial, USB, FTDI or HID needs a driver installed on the machine running the protocol, and the FTDI backend in the centrifuge example asks for a device id, which means the hardware has to be identified correctly before anything works. There is also an open question the documentation does not answer, which is how the SDK behaves when a step fails mid-protocol, since nothing in the documentation describes partial state recovery on a deck.

## Conclusion

Adopt PyLabRobot if you run several vendors of lab equipment and want protocols that are Python rather than vendor GUI scripts, and if your bench hardware can be reached over the transports it supports. Skip it if your lab standardises on a single vendor whose own software already does what you need, since the value here is the abstraction, not the automation. Before you commit, check the supported machines list for the exact models you own, and read the installation page for the extras that match your hardware, because the serial, usb, ftdi and opentrons extras are what decide whether your instrument can be reached at all.

## FAQ

### Which lab equipment does PyLabRobot support?

For liquid handling it currently supports Hamilton STAR, Hamilton Vantage, Tecan Freedom EVO and Opentrons OT-2, with more planned. It also covers plate readers, centrifuges, pumps, scales, heater shakers, fans and thermocyclers through vendor backends, and the documentation links a supported machines page.

### How do I run the same protocol on different liquid handling robots?

Keep the protocol and change the backend and the deck layout file. The Hamilton STAR example uses `STARBackend()` with a Hamilton layout JSON, the Opentrons uses `OpentronsOT2Backend(host="x.x.x.x")` with an Opentrons layout, and the Tecan uses `EVOBackend()` with a Tecan layout.

### Which PyLabRobot install extras do I need?

It depends on the transport. `serial` adds pyserial, `usb` adds pyusb and libusb-package, `ftdi` adds pylibftdi and pyusb, `hid` adds the hid package, and `opentrons` adds the OT-2 HTTP client. The `all` extra pulls most of them together, and the project requires Python 3.10 or newer.

### Can I develop a PyLabRobot protocol without a physical robot?

Yes. The project provides a browser-based Visualizer that can visualise the state of the deck during a run and can be used to develop and test protocols without a physical robot. Protocols are also written as atomic commands that can be run interactively in a Jupyter notebook or the Python REPL.

## Sources

- [License: MIT](https://github.com/PyLabRobot/pylabrobot/blob/main/LICENSE)
- [Project website](https://docs.pylabrobot.org)
- [PyLabRobot/pylabrobot on GitHub](https://github.com/PyLabRobot/pylabrobot)
- [README](https://github.com/PyLabRobot/pylabrobot/blob/main/README.md)
- [Releases](https://github.com/PyLabRobot/pylabrobot/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pylabrobot-pylabrobot
