Klipper macros are Jinja2 templates, so conditions, loops and math use Jinja2 syntax with single braces. Each example below is a complete macro you can paste into printer.cfg.
A Klipper macro uses Jinja2 conditionals: {% if condition %}, then optional {% elif condition %} and {% else %} branches, closed with {% endif %}. Put each tag on its own line inside the gcode: block. Klipper sends only the G-code in the matching branch.
[gcode_macro HOME_IF_NEEDED]
description: Home only the first time
gcode:
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% else %}
{action_respond_info("Already homed")}
{% endif %}
printer.toolhead.homed_axes is a string of the axes Klipper considers homed, and it reads "xyz" once all three are done. The Klipper status reference lists every field you can test.
Klipper macro syntax
Klipper evaluates the gcode: option with Jinja2. Its command templates page says you evaluate expressions “by wrapping them in { } characters or use conditional statements wrapped in {% %}.” Four syntax rules apply:
- Single braces for values. Klipper sets Jinja2’s expression markers to
{ } in its source, so write {params.S}, not the {{ }} you see in generic Jinja2 examples.
- Indent every line under
gcode:. The docs note that gcode: “always starts at the beginning of the line and subsequent lines in the G-Code macro never start at the beginning.”
- Comment with whole
# lines. Klipper’s config parser treats # and ; as the start of an inline comment. A Jinja2 comment like {# note #} loses its closing #} and the macro fails to load.
- Parameters arrive as upper-case strings. Klipper says parameter names “are always in upper-case” and “always passed as strings.” Convert with
|int or |float before you compare or calculate.
Klipper’s requirements file pins Jinja2 2.11.3, so the Jinja 2.11 template documentation is the reference for everything below. Reading params is covered in depth under macro parameters.
How to write an if statement in a Klipper macro
An if takes any Jinja2 expression: comparisons (==, !=, <, >=), and, or, not, and in for lists and strings.
[gcode_macro SAFE_PURGE]
gcode:
{% set min_temp = params.MIN|default(200)|float %}
{% if printer.extruder.temperature >= min_temp and printer.extruder.can_extrude %}
G92 E0
G1 E10 F300
{% else %}
{action_raise_error("Hotend below %.0f C, purge skipped" % min_temp)}
{% endif %}
can_extrude is true once the hotend passes min_extrude_temp. action_raise_error stops this macro and any macro that called it. To test whether a config section exists before using it, check {% if "bed_mesh" in printer %}.
An if cannot react to a command earlier in the same macro. Klipper states that macros “are first evaluated in entirety and only then are the resulting commands executed.” A G28 followed by a homed_axes test still sees the old value. Put the check in a second macro, which Klipper evaluates only when it is invoked, or block with a wait command.
Else if in a Klipper macro
Jinja2 spells else-if as elif. Writing {% else if %} is a syntax error: Klipper refuses to load the macro and reports “Error loading template” with the line number.
[gcode_macro MATERIAL_FAN]
gcode:
{% set material = params.MATERIAL|default("PLA")|upper %}
{% if material == "PLA" %}
M106 S255
{% elif material == "PETG" %}
M106 S128
{% elif material in ["ABS", "ASA"] %}
M107
{% else %}
{action_respond_info("Unknown material " ~ material ~ ", fan unchanged")}
{% endif %}
The fan values are examples. ~ joins strings in Jinja2.
For loops in Klipper macros
A {% for %} loop repeats its body once per item in a list or range(). Klipper’s own wipe example nests two of them. Inside a loop, loop.index counts from 1, and loop.first and loop.last mark the ends.
[gcode_macro REPORT_HEATERS]
gcode:
{% for name in printer.heaters.available_heaters %}
{action_respond_info("%s: %.1f / %.1f" % (name, printer[name].temperature, printer[name].target))}
{% endfor %}
available_heaters lists full section names such as heater_generic chamber, which is why the loop reads printer[name] with brackets.
A variable set inside a loop does not survive it. Jinja2’s docs say loop assignments “cannot outlive the loop scope.” Use a namespace to carry a result out:
[gcode_macro CHECK_COOL]
gcode:
{% set ns = namespace(hot=false) %}
{% for name in printer.heaters.available_heaters %}
{% if printer[name].temperature > 50 %}
{% set ns.hot = true %}
{% endif %}
{% endfor %}
{% if ns.hot %}
{action_respond_info("A heater is still above 50 C")}
{% endif %}
Does Klipper support while loops in macros?
No. Klipper macros are Jinja2 templates, and Jinja2 has no while statement, no break and no continue. A macro also cannot call itself. Use a for loop over range() when you know the maximum count, and a delayed_gcode that reschedules itself when you need to repeat until the printer reaches some state.
Klipper rejects recursion with “Macro … called recursively”. Break and continue need a Jinja2 extension that Klipper does not load. To skip items, filter the loop: {% for name in printer.heaters.available_heaters if name != "extruder" %}.
Even if Jinja2 had a while loop, it would spin on stale data, because the printer state is read once at evaluation. A delayed_gcode re-evaluates on every run, so it sees fresh values:
[delayed_gcode bed_cool_check]
gcode:
{% if printer.heater_bed.temperature > 40 %}
UPDATE_DELAYED_GCODE ID=bed_cool_check DURATION=10
{% else %}
{action_respond_info("Bed below 40 C, safe to remove the print")}
{% endif %}
Start it from your end macro with UPDATE_DELAYED_GCODE ID=bed_cool_check DURATION=10. Klipper’s docs show the same self-rescheduling pattern. DURATION=0 cancels it. To wait on a temperature, TEMPERATURE_WAIT blocks without a loop.
Math in Klipper macros
Jinja2 supports +, -, *, / (always returns a float), // (integer division), % (remainder) and ** (power). Convert parameters first, because a string compared with a number raises “Error evaluating” at run time.
[gcode_macro SET_FAN_PERCENT]
gcode:
{% set pct = params.P|default(100)|float %}
{% set pct = [0, [pct, 100]|min]|max %}
M106 S{(pct * 2.55)|round|int}
That macro clamps the value to 0 to 100 with the min and max filters, then scales it to the 0 to 255 range M106 takes. round returns a float even at zero precision, so pipe it through int. Other useful filters are abs and round(2). For formatted output, Python’s % string formatting works, as in Klipper’s own "%.1f" % value example. Jinja2 has no math module. Use ** 0.5 for a square root; trigonometry has no equivalent.
How do you print a message to the console from a Klipper macro?
Add a [respond] section to printer.cfg, then use RESPOND MSG="your text" or M118 your text in the macro. Both print in order with the macro’s other commands. Without [respond], use {action_respond_info("your text")}, which needs no config but prints when the macro is evaluated, before its commands run.
[respond]
[gcode_macro HEAT_REPORT]
gcode:
{% set bed = params.BED|default(60)|int %}
RESPOND MSG="Heating bed to {bed} C"
M190 S{bed}
RESPOND MSG="Bed at temperature"
The standard Mainsail (mainsail.cfg) and Fluidd (client.cfg) config files already include [respond], so check your includes before adding it.
The output forms, from Klipper’s G-code and command template references:
| Command |
Console prefix |
Use |
RESPOND MSG="…" or M118 … |
echo: (set by default_type) |
normal messages |
RESPOND TYPE=command MSG="…" |
// |
host actions such as prompts |
RESPOND TYPE=error MSG="…" |
!! |
errors; the macro keeps running |
{action_respond_info("…")} |
// |
messages computed at evaluation |
{action_raise_error("…")} |
!! |
stops the macro and its callers |
M117 is different: it sets the display message, not the console. Klipper notes that action commands run “at the time that the macro is evaluated, which may be a significant amount of time before the generated g-code commands are executed.” Written with action_respond_info, the macro above would print “Bed at temperature” before the bed starts heating. RESPOND prints it once the bed is hot.