Overview
At our August 2025 FGF User Group meeting, our software engineer Mitchell Mashburn gave a live presentation on how to use Klipper macros, command templates, and shell commands to unlock new levels of customization on your bot. If you weren’t able to attend, or if you’d like a written walkthrough to follow along with the video, this article is for you. This article is written for beginners as well as advanced users. We’ll use analogies, step-by-step examples, and ready-to-copy macros so you can experiment right away. By the end, you’ll have the knowledge to not only understand the macros in Mitchell’s demo, but also build your own custom routines to make your Gigabot truly yours. Be sure to tune into all future FGF talks and sign up here today!
Tools Needed
- Computer/Laptop
- Ethernet Cable (optional)
- Keyboard (optional)
Best Option
Second Option
Direct Ethernet Connection: If your printer isn’t networked, you can still connect a computer directly to the Raspberry Pi using an Ethernet cable and treat it like a local device. Follow our guide on how to complete this here: How to Connect a Computer to the Raspberry Pi over an Ethernet Cable as a Local Device.
Third Option
USB Keyboard: If neither option is available, you can plug a USB keyboard directly into the touchscreen.
Note: This is only relevant to Gigabots operating with an Archimajor board, this tutorial is not suitable for Marlin bot users
What is Klipper?
Explanation of Klipper Ecosystem
Klipper is the firmware that powers your printer, handling the precise, real-time control of motors, heaters, and sensors on your bot. But Klipper is just one part of a larger ecosystem. Alongside it, Moonraker serves as the application programming interface (or API) that connects Klipper to the outside world and Mainsail provides the web-based interface you use to run your printer. All of these components are aptly named to draw comparison to a sailing ship: Klipper is a named after a 19th century vessel originally built for speed; Moonraker is a light sail that is built into the top of the mast to improve speed; and Mainsail is the principal sail of the ship.
In practice, these three parts constantly communicate with each other. Your browser runs Mainsail, which talks to Moonraker on the Raspberry Pi. Moonraker acts as the middleman, translating your clicks and commands into actions Klipper understands. Klipper itself runs partly on the Pi and partly on the printer’s control board, where it translates those instructions into actual motor steps and temperature adjustments.
Klipper Commands
Simple Macros
Klipper lets you write macros, which is essentially a shortcut that behaves like a built-in gcode command. Macros can be simple or complex, and they can even reach outside the printer to talk to Linux or the internet. To get started, navigate to the side bar and click the wrench (“machine”) icon. Next, find the folder that says “macros. cfg” and click to open.
How do I write my first macro?
Now we will move on to an example macro that we can try out in our Macros.cfg folder. The following example is of a MOVE_SEQUENCE macro that will first home the bot and then send it to 3 different coordinate points on the bed. If you are new to macros, it’s best to sandbox them first. Sandboxing means prefixing each line with M118, which prints the command to the console instead of moving the printer. This way, you can confirm the expected behavior on your touchscreen or in the terminal before running it live.
Mitchell illustrates this in the video with the following MOVE_SEQUENCE example (feel free to copy and paste the following code block into your macros.cfg folder):
[gcode_macro MOVE_SEQUENCE]
gcode:
M118 G28 ;home all
M118 G1 X345 Y630 ;move to coordinates (345, 630)
M118 G1 X20 Y300 ;move to coordinates (20, 300)
M118 G1 X50 Y550 ;move to coordinates (50, 550)
From here, you can either click the button on Mainsail directly or enter the name in the console to run it directly.
Watch Mitchell demonstrate the process on how to set up your first macro (Timestamp - 6:50)
Macros can also be integrated directly into your slicer workflow by placing their names in the Start/End Gcode fields, or by using a post-processing script to insert them automatically at certain points (such as before supports or at a layer change). When you’re ready to move from testing to execution, simply remove the M118 lines and the printer will then carry out the actual moves or temperature changes as written. Essentially, macros are the core of customization. Let’s go beyond “what they look like” into how they work and how you can think about them.
Complex Command Templates
Objects
Klipper also gives you access to deeper objects inside the firmware. These objects let you read, monitor, and even act on real-time data from your printer. Think of them as variables you can call directly in macros or commands to expand what your printer can “know” about itself. To demonstrate how this works in practice, Mitchell shared a simple macro called TEMP_READ. This macro queries the extruder object and prints both the live temperature and the current target setpoint directly to the console or screen:
Mitchell’s TEMP_READ object macro:
# ----- Objects------------------------------------------------
[gcode_macro TEMP_READ]
gcode:
M118 Metering Zone Temperature: (printer.extruder.temperature)
M118 Metering Zone Target: {printer.extruder.target}
When run, this macro will display two lines in your terminal. One showing the actual reading from the hotend, and one showing the target temperature the printer is trying to reach. It’s a quick way to check if you’re on track without manually looking through graphs.
Watch Mitchell demonstrate the process on how to set up TEMP_READ macro (Timestamp - 10:49)
Conditionals
With Klipper you can add conditionals, which let your printer make decisions on the fly without directly instructing it to do so each time. Think of conditionals like “if/else” statements in programming: if this is true, do that… otherwise, do something else. Below is a simple example Mitchell created that checks whether the number you imputed is greater than 4:
Mitchell’s CONDITIONALS macro template:
[gcode_macro CONDITIONALS]
gcode:
{% set input = params.INPUT|default(5)|int %}
{% if input > 4 %}
RESPOND TYPE=echo MSG='Success'
{% else %}
RESPOND TYPE=error MSG='Fail'
{% endif %}
The input value is treated as a parameter, which means you can pass a number when running the macro (for example, CONDITIONALS INPUT=3 or CONDITIONALS INPUT=6). If you don’t provide anything, Klipper automatically uses the default value of 5. The macro then checks that number: if it’s greater than 4, you’ll see “Success” in the console; if it’s 4 or less, it will respond with “Fail.” This is a simple way to show how decision-making works inside Klipper.
Now let’s look at Mitchell’s example for conditionals when applied to a special use-case in Klipper: Preparing the printer for PETG printing. Instead of hard-coding temperature commands every time, you can use conditionals to check whether your extruders are already at the correct temperature for PETG printing.
Mitchell’s PREPARE_PETG macro template:
[gcode_macro PREPARE_PETG]
gcode:
{% set metering_zone = printer.extruder.target %}
{% set transition_zone = printer.extruder1.target %}
{% set feed_zone = printer.extruder2.target %}
{% if metering_zone != 220 and transition_zone != 210 and feed_zone != 190 %}
M118 M109 S220 T0
M118 M109 S210 T1
M118 M109 S190 T2
{% else %}
M118 Heaters at correct target temperature ...
{% endif %}
{% if "xyz" not in printer.toolhead.homed_axes %}
M118 Printer not homed, homing all now ...
G28
{% else %}
M118 Printer already homed, skipping ...
{% endif %}
In this example, three variables are created to represent the target temperatures for each extruder zone. The macro then compares those values against the desired PETG setpoints of 220, 210, and 190. If any of the zones aren’t at the correct temperature, it sends commands to heat them up; if they’re already correct, it simply reports back. After that, the macro checks whether the printer is homed. If it isn’t, it runs a full homing sequence, but if the axes are already homed, it skips the step. This way, the printer avoids wasting time by reheating to the same temperature or repeating homing unnecessarily, and instead makes a decision based on its current state.
Watch Mitchell demonstrate the process on how to set up CONDITIONALS (Timestamp - 12:02)
Loops
In standard gcode, every instruction is written line by line, which means that if you want an action to repeat you would normally have to copy and paste the same commands over and over again. In Klipper macros, you can use Jinja2 template logic to make that repetition happen automatically. A loop ({% for … %}) tells Klipper: run the following block multiple times, once for each value in a sequence.
Mitchell’s LOOPS macro template:
[gcode_macro LOOPS]
gcode:
{% set count = 10 %}
{% for i in range(count) %}
M118 {i}
G4 P1000
{% endfor %}
In this example, when you click the LOOPS macro it prints numbers 0–9 to the console, pausing for one second (G4 P1000) between each line. It’s a simple way to see how loops expand repeated commands without writing them out manually.
Now let’s take a look at Mitchell’s next loop example applied to an actual macro for a nozzle wiping procedure, where the nozzle moves back and forth across the bed to clear off any oozing filament before a print starts.
Mitchell’s NOZZLE_WIPE macro template:
[gcode_macro NOZZLE_WIPE]
gcode:
G91
{% set wipe_count = 4 %}
{% for _ in range(wipe_count) %}
M118 G1 X10
G4 P500
M118 G1 X-10
G4 P500
{% endfor %}
In this case, the macro first switches the printer into relative motion mode with G91, which tells the printer that all future moves should be interpreted as offsets from the current position rather than absolute coordinates. A variable called wipe_count is then defined as being set to 4, so the loop will run four times. Inside the loop, the nozzle is instructed to move +10 mm in the X direction, pause for half a second (G4 P500), move -10 mm back to its starting point, and pause again. Each cycle completes one full wipe, and the loop repeats this process until all four wipes are finished. By changing the value of wipe_count, you can quickly adjust how many back-and-forth passes the nozzle will make without editing the gcode itself.
Another way loops can be applied is through parameterized helper macros that handle calculations you’d otherwise need to do manually. Mitchell demonstrates this with the EXTRUDER_ROTATION macro. Instead of hand-calculating extrusion distances every time you want to adjust the value, this macro reads your calibrated rotation distance and automatically works out the math using the RPM and revolutions you provide.
Mitchell’s EXTRUDER_ROTATION macro template:
# ----- Extruder Revolutions / RPM Example
# Note: you will need to set a gear_ratio in your [extruder] config
[gcode_macro EXTRUDER_ROTATION]
gcode:
{% set revolutions = params.REVOLUTIONS|default(1)|float %}
{% set rpm = params.RPM|default(10)|int %}
{% set rotation_distance = printer.configfile.settings.extruder.rotation_distance %}
M118 G1 E{revolutions * rotation_distance} F(rpm * rotation_distance * 60]
This helper macro takes parameters for REVOLUTIONS and RPM so you don’t have to calculate extrusion distances by hand. When you run it in Mainsail, input boxes for REVOLUTIONS and RPM will appear. For example, if you enter into both input boxes: revolutions = 2 and RPM = 12, this will rotate the extruder two full turns at 12 RPM. Behind the scenes, the macro figures out two things: how much filament to push and how fast to push it. It multiplies your extruder’s calibrated rotation_distance by the number of turns to get the extrusion length (the E value). Then it takes the requested RPM, multiplies it by the same rotation_distance, and multiplies by 60 to convert from rotations per second into rotations per minute for the feedrate (the F value). This gives you one complete G1 command without doing any math yourself.
Watch Mitchell demonstrate the process on how to set up LOOPS (Timestamp - 15:50)
Shell Commands/API Requests
In normal macros, Klipper executes gcode strictly in order: one line finishes before the next begins. That means if you have a huge macro, the printer will stay put until it’s done. Shell commands solve this by letting you offload work to Linux (your Raspberry Pi), so the printer can keep moving. Mitchell explains that this makes it possible to drip-feed long or slow tasks to the Pi instead of stuffing them into gcode, or even bridge Klipper to anything your operating system can reach (such as files, web APIs, or custom scripts.)
Mitchell’s HELLO_WORLD shell command template:
# ----- Shell Commands
[gcode_shell_command run_script]
command: python /home/pi/hello_world.py
timeout: 30.0
verbose: True
[gcode_macro HELLO_WORLD]
gcode:
RUN_SHELL_COMMAND CMD=run_script
[gcode_shell_command run_script] defines a reusable command called run_script. This tells Klipper exactly what Linux should do—in this case, run the Python script located at /home/pi/hello_world.py. The timeout: 30.0 line means Klipper will only allow the script to run for 30 seconds before force-killing it, preventing a frozen or runaway process. The verbose: True option makes Klipper print the script’s output to the console, which is very helpful for debugging purposes. Finally, [gcode_macro HELLO_WORLD] is the macro you can run from Mainsail/Fluidd or the console. When triggered, it executes RUN_SHELL_COMMAND CMD=run_script, which hands the task off to Linux. The Pi runs the script, prints the message to your console, and then finishes—without ever blocking your printer’s motion queue.
Post Requests (API Requests)
Shell commands also allow Klipper to talk to web APIs using POST requests. POST requests are used to send data or trigger an action. For example, you might want your printer to send a webhook every time a layer changes so you get a notification in Discord, Slack, or Home Assistant.
Mitchell’s POST_REQUEST shell command template:
# ----- Post Requests
[gcode_shell_command post_request]
command: python /home/pi/post_request.py
timeout: 1.0
verbose: False
[gcode_macro POST_REQUEST]
gcode:
RUN_SHELL_COMMAND CMD=post_request
This setup defines a shell command called post_request that runs a Python script whenever it’s called. When you run POST_REQUEST in Mainsail or the console, Klipper looks up this definition and tells the Pi to execute python /home/pi/post_request.py. The timeout is set to 1.0 because a POST request usually completes in a fraction of a second. If it takes longer than one second, Klipper cancels it automatically to keep the system responsive and avoid piling up stuck processes. The verbose: False option keeps your console clean by hiding the raw response from the server (though you could set it to True while debugging). In practice, this lets you trigger actions outside the printer all while your print keeps running uninterrupted.
Get Requests (API Requests)
A GET request is the simplest type of API request. It’s used when you just want to ask for information from a server without changing anything. You enter your request URL into your browser’s address bar, and the server replies with data.
Mitchell’s GET Request Example:
http://<printer-name>.local/printer/objects/query?extruder
OR
http://<IP Address>/printer/objects/query?extruder
http://<printer-name>.local → the network address of your printer’s Pi (replace <printer-name> with whatever your system uses, like gigabot.local).
/printer/objects/query → the Moonraker API endpoint that lets you ask about printer objects (extruder, bed, fans, sensors, etc.).
?extruder → this is the query part of the URL that tells Moonraker to retrieve information about the extruder
This is what the resultant page would look like:
At timestamp 104802s, the extruder is holding steady at 232.7 °C with a target of 230 °C, using about 26% heater power. Extrusion is allowed, pressure advance is set to 0.11, smoothing time is 0.04 seconds, and there’s no active motion queue data.
Watch Mitchell demonstrate the process on how to set up Shell Commands and API Requests (Timestamp - 20:35)
Conclusion
We’ve only scratched the surface of what’s possible with Klipper macros, objects, conditionals, loops, and shell commands. If you’d like to dive deeper, be sure to tune in to the remainder of Mitchell’s FGF User Group talk, where he walks through additional Arduino–Klipper examples and real-world implementation use cases.
And as always, if you have any questions about getting started or customizing your own workflows, don’t hesitate to reach out to the re:3D Customer Support team at support@re3d.org. To keep learning and stay connected, be sure to check out and join all future FGF talks.
Taylor Boutte
Blog Post Author
