How a 3D printer toolchanger works

How to configure Klipper for a toolchanger

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.

Published · Last verified

A RAMPS 3D printer controller board stacked on an Arduino Mega, with four stepper driver modules, heatsinks and green screw terminals, on a white desk
Photo: Tony Buser, CC BY-SA 2.0

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.

How do you configure Klipper for a toolchanger?

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.

How to set up klipper-toolchanger, step by step

  1. 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.
  2. Install the plugin. Run the install script over SSH (command below). It links the plugin’s Python modules into Klipper.
  3. Add the update entry. Paste the [update_manager klipper-toolchanger] block into moonraker.conf so updates appear in Mainsail or Fluidd.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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.

Installing the klipper-toolchanger plugin

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.

The [toolchanger] section: pickup and dropoff G-code

[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.

The [tool] sections and T0/T1 macros

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

Setting tool offsets in the config

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.

What is klipper-toolchanger-easy?

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.

How to uninstall klipper-toolchanger

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:

  1. 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.
  2. Remove the Moonraker entry. Delete the [update_manager klipper-toolchanger] (or klipper-toolchanger-easy) block from moonraker.conf.
  3. 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.
  4. 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.
  5. Restart. Run sudo systemctl restart klipper moonraker and check the Klipper log for config errors.

Sources

  1. viesturz/klipper-toolchanger, README (accessed October 14, 2026)
  2. viesturz/klipper-toolchanger, toolchanger.md (config and G-code reference) (accessed October 14, 2026)
  3. viesturz/klipper-toolchanger, install.sh (accessed October 14, 2026)
  4. viesturz/klipper-toolchanger, macros.cfg (accessed October 14, 2026)
  5. viesturz/klipper-toolchanger, examples/README.md (accessed October 14, 2026)
  6. viesturz/klipper-toolchanger, examples/tool_paths.md (accessed October 14, 2026)
  7. viesturz/klipper-toolchanger, fixed dock toolchanger.cfg example (accessed October 14, 2026)
  8. viesturz/klipper-toolchanger, examples/T0.cfg (accessed October 14, 2026)
  9. viesturz/klipper-toolchanger, examples/printer.cfg (accessed October 14, 2026)
  10. viesturz/klipper-toolchanger, klipper/extras/tool.py (accessed October 14, 2026)
  11. viesturz/klipper-toolchanger, issue #31: Uninstal Script (accessed October 14, 2026)
  12. viesturz/tapchanger, Klipper README (accessed October 14, 2026)
  13. jwellman80/klipper-toolchanger-easy, repository (accessed October 14, 2026)
  14. jwellman80/klipper-toolchanger-easy, README (accessed October 14, 2026)
  15. jwellman80/klipper-toolchanger-easy, install.sh (accessed October 14, 2026)
  16. jwellman80/klipper-toolchanger-easy, upgrade.md (accessed October 14, 2026)
  17. DraftShift Design, StealthChanger wiki: Installation (accessed October 14, 2026)
  18. DraftShift Design, StealthChanger wiki: Configuration (accessed October 14, 2026)
  19. DraftShift Design, StealthChanger wiki: Slicers (accessed October 14, 2026)
  20. DraftShift Design, StealthChanger Klipper example files (accessed October 14, 2026)
  21. Klipper source, klippy/configfile.py (accessed October 14, 2026)