Disclaimer: This is an independent resource site.
Not affiliated with the Voron project or its development team.
Klipper's `save_variables` feature allows you to persist data between restarts, firmware reloads, and even across prints. For Voron owners, this opens up powerful capabilities: per-filament profiles that survive reboots, Z offset memory that persists after bed changes, calibration data that doesn't need re-running, and much more.
---
## What Are Save Variables?
`save_variables` is a Klipper system that writes key-value pairs to a JSON file on disk. Unlike regular macro variables (which reset on `RESTART` or power cycle), save variables are written to storage and reloaded automatically when Klipper starts.
**Key capabilities:**
- Store calibration results permanently
- Remember user preferences across sessions
- Implement per-filament profiles
- Track usage statistics (filament consumed, print hours)
- Persist Z offset adjustments
---
## Enabling Save Variables
Add this to your `printer.cfg`:
[save_variables]
filename: ~/printer_data/config/saved_variables.cfg
The `.cfg` extension is intentional — the file doubles as a Klipper config file, allowing you to `[include]` it and use the stored values as regular config parameters.
**Important:** The file is created automatically when variables are first saved. You do not need to create it manually.
---
## Basic Usage — Saving and Loading Variables
### Saving a Variable
Use `SAVE_VARIABLE` in any macro or from the console:
SAVE_VARIABLE VARIABLE=my_offset VALUE=0.175
The value is written to `saved_variables.cfg` as:
#*# [my_offset]
#*# 0.175
### Loading Variables in Macros
Access saved variables through the `printer.save_variables` namespace:
[gcode_macro APPLY_Z_OFFSET]
gcode:
SET_GCODE_OFFSET Z_ADJUST={printer.save_variables.variables.my_offset}
### Checking If a Variable Exists
Use Jinja2's default filter or conditional:
{% set z_offset = printer.save_variables.variables.my_offset | default(None) %}
{% if z_offset is not None %}
SET_GCODE_OFFSET Z_ADJUST={z_offset}
{% endif %}
---
## Practical Applications for Voron Printers
### 1. Per-Filament Z Offset Profiles
Different filaments (PLA vs ABS vs PETG) often need different first-layer Z offsets. Store them persistently:
[gcode_macro SAVE_Z_OFFSET]
description: Save current Z offset for a filament type
gcode:
{% set filament = params.FILAMENT|default("PLA") %}
{% set offset = printer.gcode_move.homing_origin.z|default(0) %}
SAVE_VARIABLE VARIABLE=z_offset_{filament} VALUE={offset}
M117 Z offset saved for {filament}
[gcode_macro LOAD_Z_OFFSET]
description: Load saved Z offset for a filament type
gcode:
{% set filament = params.FILAMENT|default("PLA") %}
{% set var_name = "z_offset_" ~ filament %}
{% set offset = printer.save_variables.variables[var_name] | default(None) %}
{% if offset is not None %}
SET_GCODE_OFFSET Z_ADJUST={offset}
M117 Loaded Z offset for {filament}: {offset}mm
{% else %}
M117 No Z offset saved for {filament}
{% endif %}
**Usage in `PRINT_START`:**
[gcode_macro PRINT_START]
gcode:
# ... homing, heating ...
{% set filament_type = params.FILAMENT_TYPE|default("PLA") %}
{% set var_name = "z_offset_" ~ filament_type %}
{% set saved_offset = printer.save_variables.variables[var_name] | default(None) %}
{% if saved_offset is not None %}
SET_GCODE_OFFSET Z_ADJUST={saved_offset}
M117 Using {filament_type} Z offset: {saved_offset}mm
{% endif %}
### 2. Per-Filament Pressure Advance Values
Save pressure advance values for each filament brand/type:
[gcode_macro SAVE_PA]
description: Save pressure advance for current filament
gcode:
{% set filament = params.FILAMENT|default("default") %}
{% set pa = printer.configfile.settings.extruder.pressure_advance %}
SAVE_VARIABLE VARIABLE=pa_{filament} VALUE={pa}
M117 PA saved for {filament}: {pa}
[gcode_macro APPLY_PA]
description: Apply saved pressure advance for a filament
gcode:
{% set filament = params.FILAMENT|default("default") %}
{% set var_name = "pa_" ~ filament %}
{% set pa = printer.save_variables.variables[var_name] | default(None) %}
{% if pa is not None %}
SET_PRESSURE_ADVANCE ADVANCE={pa}
M117 PA set to {pa} for {filament}
{% endif %}
### 3. Persistent Z Offset Adjustment (Live Tweak)
Adjust Z offset during a print and have it persist:
[gcode_macro Z_OFFSET_UP]
description: Increase Z offset by 0.01mm
gcode:
{% set current = printer.save_variables.variables.z_offset_live | default(0.0) %}
{% set new = current + 0.01 %}
SET_GCODE_OFFSET Z_ADJUST={new}
SAVE_VARIABLE VARIABLE=z_offset_live VALUE={new}
M117 Z offset: {new}mm
[gcode_macro Z_OFFSET_DOWN]
description: Decrease Z offset by 0.01mm
gcode:
{% set current = printer.save_variables.variables.z_offset_live | default(0.0) %}
{% set new = current - 0.01 %}
SET_GCODE_OFFSET Z_ADJUST={new}
SAVE_VARIABLE VARIABLE=z_offset_live VALUE={new}
M117 Z offset: {new}mm
### 4. Print Start/Resume Tracking
Monitor printer usage:
[gcode_macro PRINT_START]
gcode:
# ... standard start stuff ...
{% set total_hours = printer.save_variables.variables.total_print_hours | default(0.0) %}
M117 Total print time: {"%.1f"|format(total_hours)} hours
[gcode_macro PRINT_END]
gcode:
# ... standard end stuff ...
{% set start_time = printer.save_variables.variables.print_start_time | default(0) %}
{% set elapsed = (session.start_time - start_time) / 3600 %}
{% set total = printer.save_variables.variables.total_print_hours | default(0.0) %}
SAVE_VARIABLE VARIABLE=total_print_hours VALUE={total + elapsed}
SAVE_VARIABLE VARIABLE=last_print_duration VALUE={elapsed}
SAVE_VARIABLE VARIABLE=total_prints VALUE={(printer.save_variables.variables.total_prints | default(0)) + 1}
M117 Print complete! Total prints: {(printer.save_variables.variables.total_prints | default(0))}
### 5. Bed Mesh Profile Management
Save and recall multiple bed mesh profiles:
[gcode_macro SAVE_MESH_PROFILE]
description: Save current bed mesh with a name
gcode:
{% set name = params.NAME|default("default") %}
BED_MESH_PROFILE SAVE={name}
SAVE_VARIABLE VARIABLE=last_mesh_profile VALUE="{name}"
M117 Mesh saved as {name}
[gcode_macro RESTORE_LAST_MESH]
description: Load the last used bed mesh profile
gcode:
{% set profile = printer.save_variables.variables.last_mesh_profile | default("default") %}
BED_MESH_PROFILE LOAD="{profile}"
M117 Loaded mesh: {profile}
---
## The saved_variables.cfg File
Your saved variables file lives at the path you specified. Example contents:
#*# [variables]
#*# z_offset_PLA = 0.125
#*# z_offset_ABS = 0.050
#*# z_offset_PETG = 0.075
#*# pa_PLA = 0.045
#*# pa_ABS = 0.035
#*# pa_PETG = 0.060
#*# total_print_hours = 287.4
#*# total_prints = 143
#*# last_print_duration = 4.2
#*# last_mesh_profile = "250c_abs"
#*# z_offset_live = 0.010
**Important notes:**
- The `#*# ` prefix marks auto-generated entries — Klipper uses this to identify managed variables
- Do NOT edit this file manually while Klipper is running, or your changes will be overwritten
- You can edit it safely when Klipper is stopped, but be careful with JSON syntax
**To include the file in your config:**
[include saved_variables.cfg]
This is optional but recommended if you reference saved variables in other config sections.
---
## Advanced Techniques
### Namespacing Variables
For complex setups, namespace your variables to avoid collisions:
SAVE_VARIABLE VARIABLE=filament:pla:z_offset VALUE=0.125
SAVE_VARIABLE VARIABLE=filament:abs:pa VALUE=0.035
SAVE_VARIABLE VARIABLE=calibration:input_shaper:x_freq VALUE=47.5
Access them as:
printer.save_variables.variables["filament:pla:z_offset"]
### Batch Saving
Save multiple related values at once (call `SAVE_VARIABLE` multiple times):
[gcode_macro SAVE_FILAMENT_PROFILE]
description: Save complete filament profile
gcode:
SAVE_VARIABLE VARIABLE=f_{params.NAME}_z_offset VALUE={params.Z_OFFSET}
SAVE_VARIABLE VARIABLE=f_{params.NAME}_pa VALUE={params.PA}
SAVE_VARIABLE VARIABLE=f_{params.NAME}_temp VALUE={params.TEMP}
SAVE_VARIABLE VARIABLE=f_{params.NAME}_bed_temp VALUE={params.BED_TEMP}
SAVE_VARIABLE VARIABLE=f_{params.NAME}_retract VALUE={params.RETRACT}
SAVE_VARIABLE VARIABLE=f_{params.NAME}_fan_speed VALUE={params.FAN}
M117 Profile {params.NAME} saved
### Reading Variables Without Including the File
Even without `[include saved_variables.cfg]`, you can access variables in macros:
{printer.save_variables.variables.my_variable}
But including the file allows you to use variables as config parameters:
[heater_bed]
# This only works if the file is included
pid_kp: {printer.save_variables.variables.bed_pid_kp | default(72.426)}
### Resetting/Clearing Variables
There's no built-in `DELETE_VARIABLE`. To reset a variable, save an empty or default value:
SAVE_VARIABLE VARIABLE=my_data VALUE=None
To clear the entire file, stop Klipper and delete/truncate `saved_variables.cfg`, then restart.
---
## Integration with Macros and Slicers
### Passing Filament Info from Slicer
OrcaSlicer and others can pass custom parameters in `PRINT_START`:
# In OrcaSlicer's filament start gcode:
; filament_type = {filament_type}
; filament_vendor = {filament_vendor}
# In your PRINT_START macro:
{% set ftype = params.FILAMENT_TYPE|default("PLA") %}
{% set fvendor = params.FILAMENT_VENDOR|default("Generic") %}
{% set profile_key = fvendor ~ "_" ~ ftype %}
{% set z_off = printer.save_variables.variables["z_" ~ profile_key] | default(0.0) %}
SET_GCODE_OFFSET Z_ADJUST={z_off}
### Moonraker Integration
Moonraker's `machine.update_manager` can also benefit from save variables:
[update_manager my_repo]
type: git_repo
path: ~/printer_data/config/my_macros
origin: https://github.com/you/your-macros.git
primary_branch: main
managed_services: klipper
managed_scripts:
- "{% if printer.save_variables.variables.auto_update | default(true) %}"
- update
- "{% endif %}"
---
## Troubleshooting
### Variables Not Persisting
**Symptom:** Variables reset after `FIRMWARE_RESTART`.
**Causes and solutions:**
1. **File path incorrect**: Verify the path in `[save_variables]` is writable. Check with:
ls -la ~/printer_data/config/saved_variables.cfg
2. **File permission issue**: The Klipper service user must have write access:
chown pi:pi ~/printer_data/config/saved_variables.cfg
3. **SD card full or read-only**: Free up space or check mount status.
### Variable Read Errors
**Symptom:** Error messages when accessing `printer.save_variables.variables.x`.
**Solution:** Always use `| default()` when accessing variables that might not exist:
{printer.save_variables.variables.my_var | default(0)}
### Corrupted Variables File
If `saved_variables.cfg` gets corrupted (e.g., power loss during save), Klipper may fail to parse it.
**Fix:** Stop Klipper, back up the corrupted file, and create a fresh empty one:
sudo systemctl stop klipper
mv ~/printer_data/config/saved_variables.cfg ~/printer_data/config/saved_variables.cfg.bak
touch ~/printer_data/config/saved_variables.cfg
sudo systemctl start klipper
You'll lose saved data but the system recovers.
---
## Performance Considerations
`save_variables` writes to disk every time `SAVE_VARIABLE` is called. Avoid calling it in tight loops:
# BAD — writes to disk 100 times
{% for i in range(100) %}
SAVE_VARIABLE VARIABLE=temp VALUE={i}
{% endfor %}
# GOOD — only final value
{% set final = 0 %}
{% for i in range(100) %}
{% set final = i %}
{% endfor %}
SAVE_VARIABLE VARIABLE=temp VALUE={final}
Write operations are atomic (Klipper writes to a temp file and renames), but excessive writes can wear SD cards over years of use.
---
## Complete Example: Filament Profile System
Here's a full example combining everything into a reusable macro system:
[gcode_macro FILAMENT_PROFILE_SAVE]
description: Save current settings as a filament profile
gcode:
{% set name = params.NAME|default("default") %}
SAVE_VARIABLE VARIABLE=fp_{name}_z_offset VALUE={printer.gcode_move.homing_origin.z|default(0)}
SAVE_VARIABLE VARIABLE=fp_{name}_pa VALUE={printer.configfile.settings.extruder.pressure_advance}
SAVE_VARIABLE VARIABLE=fp_{name}_temp VALUE={printer.configfile.settings.extruder.max_temp}
SAVE_VARIABLE VARIABLE=fp_{name}_retract VALUE={printer.configfile.settings.extruder.max_extrude_only_distance}
M117 Profile "{name}" saved
[gcode_macro FILAMENT_PROFILE_LOAD]
description: Load a saved filament profile
gcode:
{% set name = params.NAME|default("default") %}
{% set z_off = printer.save_variables.variables["fp_" ~ name ~ "_z_offset"] | default(None) %}
{% set pa = printer.save_variables.variables["fp_" ~ name ~ "_pa"] | default(None) %}
{% set temp = printer.save_variables.variables["fp_" ~ name ~ "_temp"] | default(None) %}
{% if z_off is not None %}
SET_GCODE_OFFSET Z_ADJUST={z_off}
{% endif %}
{% if pa is not None %}
SET_PRESSURE_ADVANCE ADVANCE={pa}
{% endif %}
{% if temp is not None %}
M104 S{temp}
{% endif %}
M117 Loaded profile "{name}"
---
## Summary
Klipper's `save_variables` is one of the most powerful features for Voron owners who want to move beyond basic configuration. With persistent variables you can:
- Store per-filament Z offsets and pressure advance values
- Track usage statistics and print history
- Create instant-recall filament profiles
- Persist live Z offset adjustments between prints
- Manage bed mesh profiles across different materials
The key to success is thoughtful variable naming, liberal use of `| default()`, and avoiding excessive writes to storage. Implement the examples in this guide one at a time, test thoroughly, and your Voron will reward you with a smarter, more personalized printing experience.