Klipper has no toolchanger support of its own. The klipper-toolchanger plugin adds it, and StealthChanger, TapChanger and most other Voron-style changers run on it. Here is the install, each config section with a working snippet, a toolchanger PRINT_START, the klipper-toolchanger-easy bundle, and how to take it all out again.
To configure Klipper for a toolchanger, install the klipper-toolchanger plugin and write two kinds of config section: one [toolchanger] section for the shared pickup and dropoff G-code, and one [tool Tn] section per toolhead. T0 is the reference tool for homing and offsets, and every print starts through a PRINT_START macro that picks up the first tool.
Install viesturz’s klipper-toolchanger plugin, which adds the [toolchanger] and [tool] config sections and commands such as SELECT_TOOL. Give every toolhead its own config file with its extruder, fan, park position and offsets. Copy the pickup and dropoff paths for your dock type, then set your slicer to call PRINT_START with the first tool.
Stock Klipper only switches between extruders on one carriage. The plugin moves the gantry to a dock, swaps the tool, checks it arrived and applies its offset. Start from a printer that already prints well on one tool.
- Check your Klipper version. The StealthChanger docs require Klipper newer than June 14, 2024 (commit 1591a51) and do not support DangerKlipper. klipper-toolchanger-easy requires Klipper 0.13.0-707 or later.
- Install the plugin. Run the install script over SSH (command below). It links the plugin’s Python modules into Klipper.
- Add the update entry. Paste the
[update_manager klipper-toolchanger] block into moonraker.conf so updates appear in Mainsail or Fluidd.
- Include the macros. Add the plugin’s
macros.cfg to your config. It replaces M104 and M109 so M104 T1 S220 heats tool 1, not the active tool.
- Move each hotend into its own file. Take
[extruder] out of printer.cfg and make T0.cfg, T1.cfg and so on. T0 keeps the name extruder; T1 uses extruder1, T2 extruder2.
- Write a
[tool Tn] section in each file. Set tool_number, extruder, fan, detection_pin, the three gcode_*_offset values and the params_park_x/y/z dock position.
- Add the
[toolchanger] section. Copy the example that matches your dock (fixed dock, liftbar, or liftbar with flying gantry) and fill in the pickup and dropoff path for your changer.
- Add the helper sections. The example
printer.cfg adds [force_move] with enable_force_move: True and [rounded_path], which the example pickup and dropoff G-code calls.
- Set up homing. Use the homing example for your Z probe layout: a probe on T0, a probe in each tool (usually Voron Tap on every TapChanger or StealthChanger tool), or a scanner on the shuttle.
- Align each dock. Run
TOOL_ALIGN_START, jog the toolhead into the dock, run TOOL_ALIGN_TEST to test the change, then TOOL_ALIGN_DONE to save the park position.
- Calibrate offsets and set up the slicer. Measure each tool’s offset against T0, then replace the slicer’s start G-code with the toolchanger PRINT_START line below.
The plugin lives at github.com/viesturz/klipper-toolchanger, written by Viesturs Zariņš (viesturz) while developing the TapChanger. Run the installer over SSH as your normal user, not as root:
wget -O - https://raw.githubusercontent.com/viesturz/klipper-toolchanger/main/install.sh | bash
The script stops if it runs as root or cannot find a klipper.service. It clones the repository to ~/klipper-toolchanger, symlinks every file in klipper/extras/ into ~/klipper/klippy/extras/ and restarts Klipper. The links cover toolchanger.py, tool.py, tool_probe.py, rounded_path.py and tools_calibrate.py.
Then add this to moonraker.conf:
[update_manager klipper-toolchanger]
type: git_repo
channel: dev
path: ~/klipper-toolchanger
origin: https://github.com/viesturz/klipper-toolchanger.git
managed_services: klipper
primary_branch: main
Moonraker updates pull new code but do not link new Python files into Klipper. When an update adds a module, run bash ~/klipper-toolchanger/install.sh again. Read the changelog first, because two recent releases changed how existing configs behave:
- 2025.12.26: tool offsets now use a separate transform instead of the G-code offset. Your own
SET_GCODE_OFFSET (Z baby-stepping, for example) survives tool changes.
- 2026.2.8:
detection_pin logic was inverted. Add or remove the ! on each tool’s pin when you update past this release.
[toolchanger] holds what all tools share. Any option here becomes the default for every [tool], and a tool can override it. The plugin moves nothing on its own: the motion lives in pickup_gcode and dropoff_gcode, which you copy from the example for your dock type and then tune. This is the top of the fixed-dock example, with the StealthChanger paths from the plugin’s tool_paths.md filled in:
[toolchanger]
t_command_restore_axis: Z
params_safe_y: 120 # Y where the toolhead clears the docks
params_close_y: 15 # Y to approach the dock from
params_fast_speed: 30000 # mm/min for travel between docks
params_path_speed: 900 # mm/min (15 mm/s) for the change itself
# Moves relative to each tool's park position. f = speed multiplier,
# verify = check the tool is detected at the end of that move
params_dropoff_path: [{'z':3.5, 'y':4}, {'z':0, 'y':0}, {'z':-12, 'y':0}]
params_pickup_path: [{'z':-12, 'y':2}, {'z':-12, 'y':0}, {'z':1.5, 'y':0, 'f':0.5, 'verify':1}, {'z':0.5, 'y':2.5, 'f':0.5}, {'z':8, 'y':8}]
initialize_on: home
require_tool_present: True
error_gcode:
PAUSE
Below that, the example defines the two change macros. Copy both unchanged from the example file:
dropoff_gcode raises Z by 1 mm and travels to params_safe_y, then to the tool’s park X and Z. It then plays params_dropoff_path relative to the park position.
pickup_gcode approaches from params_close_y and waits on the nozzle temperature with M109 while the tool rests in the dock. It plays params_pickup_path, runs VERIFY_TOOL_DETECTED at the step marked verify and moves back to the print.
Inside both macros you can read tool, toolchanger, pickup_tool, dropoff_tool and restore_position.
Other [toolchanger] options:
initialize_on: first-use (default), home, or manual, which waits for INITIALIZE_TOOLCHANGER.
before_change_gcode and after_change_gcode: run on every change. The example uses the after hook to apply each tool’s input shaper frequencies.
error_gcode: runs on a failed change instead of shutting Klipper down. The example pauses the print and overrides RESUME to call INITIALIZE_TOOLCHANGER first.
transfer_fan_speed: on by default, so the part fan speed carries over to the new tool.
Each toolhead gets a [tool] section and, by convention, a T<n> macro that calls SELECT_TOOL. This is T1, adapted from the plugin’s T0.cfg example:
[gcode_macro T1]
variable_color: ""
gcode:
SELECT_TOOL T=1
[tool T1]
tool_number: 1
extruder: extruder1
fan: T1_partfan # a [fan_generic T1_partfan] on the toolhead board
detection_pin: et1:PB6 # tool-present switch; check the ! after updating to 2026.2.8
gcode_x_offset: 0 # measured against T0, see below
gcode_y_offset: 0
gcode_z_offset: 0
params_park_x: 0.0 # where this tool sits in its dock
params_park_y: -7.7
params_park_z: 349.5
The explicit macro is optional. Setting tool_number alone registers a T1 command, and the plugin leaves an existing [gcode_macro T1] in place. The examples keep the macro for its color variable, which the before and after change hooks update to mark the active tool.
The plugin supplies the other commands you will use:
| Command |
What it does |
INITIALIZE_TOOLCHANGER |
Resets the toolchanger state and detects the mounted tool; required after any failed change |
SELECT_TOOL T=1 |
Drops the current tool and picks up tool 1 |
VERIFY_TOOL_DETECTED ASYNC=1 |
Checks that the expected tool is still mounted; the example slicer setup runs it after every layer |
ENTER_DOCKING_MODE, TEST_TOOL_DOCKING |
Clears offsets and docks and undocks the current tool, for aligning docks |
SET_TOOL_PARAMETER, SAVE_TOOL_PARAMETER |
Changes a params_* value live, then writes it to the config |
SET_TOOL_TEMPERATURE T=1 TARGET=220 |
Heats a tool that is not active; M104 T1 S220 calls it through macros.cfg |
Each tool’s offset from T0 goes in gcode_x_offset, gcode_y_offset and gcode_z_offset in its [tool] section. T0 is the reference and stays at 0. The plugin applies it at pickup, separately from any SET_GCODE_OFFSET of your own.
A Tap-per-tool setup has a second set: x_offset, y_offset and z_offset in each [tool_probe Tn] section. Per the StealthChanger wiki, the [tool_probe] offset applies only when homing with that tool. The [tool] offsets apply at every tool change, relative to the tool you homed with. So home, level and mesh with T0 every time.
To measure the offsets with a calibration probe, a camera or a printed test, see calibrating tool offsets on a toolchanger.
klipper-toolchanger-easy is GitHub user jwellman80’s fork of viesturz’s klipper-toolchanger. It bundles the plugin with ready-made StealthChanger configs and macros. Its install script sets up homing, tool detection, offset calibration and crash detection files, plus example tool files. You then edit one override file and each tool’s Tn.cfg.
The repository is github.com/jwellman80/klipper-toolchanger-easy; an earlier version is kept as klipper-toolchanger-easy-old. It handles installation only, points back to the StealthChanger wiki for everything else, and assumes you start from a working printer. It needs Klipper 0.13.0-707 or later.
Install it with:
cd ~/
git clone https://github.com/jwellman80/klipper-toolchanger-easy.git
cd ~/klipper-toolchanger-easy
./install.sh
The script asks how you will probe Z: option 1 is Tap on each tool, option 2 is a Beacon, Cartographer, Eddy or similar on the shuttle. It then:
- installs the Python requirements into
~/klippy-env
- links the plugin into Klipper
- links the read-only macros into
~/printer_data/config/toolchanger/readonly-configs/
- copies example tools and
toolchanger-config.cfg into ~/printer_data/config/toolchanger/, without overwriting files already there
Add one line to printer.cfg:
[include toolchanger/readonly-configs/toolchanger-include.cfg]
That file includes the shared configs, every tools/T*.cfg, and toolchanger-config.cfg last. Klipper keeps the last value it reads, so you change a setting by repeating its section and option in toolchanger-config.cfg and leave the read-only files alone.
At minimum, set each tool’s dock position in its Tn.cfg (the files must be named T0.cfg, T1.cfg and so on). Then fill in homing_override_config and _CALIBRATION_SWITCH in toolchanger-config.cfg. For Moonraker updates, reuse the [update_manager] block above with the name, path and origin changed to klipper-toolchanger-easy.
StealthChanger Klipper config and macros
StealthChanger runs on the same klipper-toolchanger plugin, so its config has the same sections. DraftShift Design’s Klipper folder holds example Toolhead_T0.cfg and Toolhead_T1.cfg files and the lines to add to printer.cfg. The homing, tool detection, macros and toolchanger files come from viesturz’s examples, or all at once from klipper-toolchanger-easy.
Points from the StealthChanger wiki that catch people out:
- Remove your old PRINT_START and PRINT_END before you start, because toolchanging needs its own versions.
- Set
t_command_restore_axis: Z.
- Number everything from 0. T1 uses
extruder1, T1_partfan and its own toolhead MCU (microcontroller board); T0’s extruder is plain extruder.
- Dock Z is the height where 1 mm lower would untrigger the Tap sensor.
close_y and safe_y: start with close_y = park Y + 30 mm, and safe_y = park Y plus the depth of your thickest tool, plus a margin.
DraftShift’s example tool files describe the docking motion as one params_sc_path, played forward for dropoff and in reverse for pickup. The current klipper-toolchanger examples use separate params_dropoff_path and params_pickup_path. Use whichever format your toolchanger.cfg reads, because a path your macros never read leaves the tool moving on defaults.
StealthChanger PRINT_START macro
A toolchanger PRINT_START has to home and level with T0 and preheat every tool the print uses. Then it picks up the first tool and waits for it to heat. This version is adapted from the PRINT_START in klipper-toolchanger-easy and the TapChanger macro, for a Voron 2.4 with Tap on each tool:
[gcode_macro PRINT_START]
gcode:
{% set TOOL = params.TOOL|default(0)|int %}
{% set TOOL_TEMP = params.TOOL_TEMP|default(200)|float %}
{% set BED_TEMP = params.BED_TEMP|default(60)|float %}
M190 S{BED_TEMP} ; heat the bed and wait
G28 ; home; with initialize_on: home this also starts the toolchanger
T0 ; level and mesh with the reference tool
QUAD_GANTRY_LEVEL ; Voron 2.4; use Z_TILT_ADJUST on a Trident
G28 Z
BED_MESH_CALIBRATE ADAPTIVE=1
M104 T0 S0 ; T0 off; reheated below if the print uses it
; preheat every tool the slicer sent a T<n>_TEMP for
{% for n in printer.toolchanger.tool_numbers %}
{% set key = 'T' ~ n|string ~ '_TEMP' %}
{% if key in params %}
M104 T{n} S{params[key]}
{% endif %}
{% endfor %}
T{TOOL} ; pick up the first tool of the print
M109 S{TOOL_TEMP} ; wait for it to reach temperature
The klipper-toolchanger-easy version also wraps the routine in STOP_CRASH_DETECTION and START_CRASH_DETECTION, from its crash-detection file. If you purge, add it after the last line. The PRINT_START macro guide covers the single-tool steps in more detail.
The slicer has to send the starting tool and each tool’s temperature. For OrcaSlicer (Printer settings > Machine G-code > Machine start G-code) and PrusaSlicer (Printer > Custom G-code > Start G-code), the StealthChanger wiki gives this start G-code. Keep it on one line:
PRINT_START TOOL_TEMP={first_layer_temperature[initial_tool]} {if is_extruder_used[0]}T0_TEMP={first_layer_temperature[0]}{endif} {if is_extruder_used[1]}T1_TEMP={first_layer_temperature[1]}{endif} {if is_extruder_used[2]}T2_TEMP={first_layer_temperature[2]}{endif} {if is_extruder_used[3]}T3_TEMP={first_layer_temperature[3]}{endif} {if is_extruder_used[4]}T4_TEMP={first_layer_temperature[4]}{endif} {if is_extruder_used[5]}T5_TEMP={first_layer_temperature[5]}{endif} BED_TEMP=[first_layer_bed_temperature] TOOL=[initial_tool]
In PrusaSlicer, also put M104 S{temperature[next_extruder]} T[next_extruder] in the Tool change G-code, so the next tool heats during the swap. Leave Orca’s Change filament G-code blank. The toolchanger slicing guide covers the rest of the slicer setup.
There is no uninstall script; a 2024 request for one on the plugin’s issue tracker was closed without one. Undo what the installer did, in this order:
- Remove the config first. Delete or comment out the
[toolchanger], [tool Tn], [tool_probe Tn], [tools_calibrate] and [rounded_path] sections and the includes that load them. Put back a single [extruder] and your old homing and PRINT_START. Otherwise Klipper stops at startup with Section 'toolchanger' is not a valid config section.
- Remove the Moonraker entry. Delete the
[update_manager klipper-toolchanger] (or klipper-toolchanger-easy) block from moonraker.conf.
- Remove the linked modules. Run
find ~/klipper/klippy/extras -lname '*klipper-toolchanger*' -delete. It deletes only the symlinks that point into the plugin’s folder, for either version.
- Delete the repository. Run
rm -rf ~/klipper-toolchanger (or ~/klipper-toolchanger-easy). For the easy version, also delete ~/printer_data/config/toolchanger/ once you have saved anything you want from it.
- Restart. Run
sudo systemctl restart klipper moonraker and check the Klipper log for config errors.