Spoolman: Filament Spool Tracking for Klipper and OctoPrint
Running Spoolman on a Pi or your own server: docker compose, wiring it to Moonraker and OctoPrint, how usage is actually deducted, QR labels, known limitations and fixing weight drift.
Spoolman is a free, self-hosted filament inventory service: a database, a REST API and a web client in a single container. It never talks to the printer itself — Moonraker (for Klipper) or an OctoPrint plugin reports the usage, and Spoolman keeps what's left on every spool in grams and meters, updating it while the print runs. It's been developed by Donkie since April 1, 2023 under the MIT license; as of August 9, 2026 it sits at 2,692 GitHub stars with v0.26.1 released on August 7, 2026.
Why track filament at all when you own a scale
With one spool, "will it last?" is answered by looking at it. Trouble starts around spool number four: three black PLAs from different brands, two of them opened, one dried and one not. The slicer confidently tells you how many grams the model needs — but it has no idea how much is left on the spool currently sitting on the holder.
A scale answers that, manually, every single time: pull the spool, weigh it, subtract the empty spool weight (193 g for Prusament, 154 g for ELEGOO, 250 g for Bambu Lab — different for every brand), write it down. A month later your notes and reality have quietly parted ways. Software tracking does the same job by itself: the printer pushed 14,077 mm of filament, so 43 g came off that spool.
- Live remaining weight. The number drops while the print runs, not after you remember to update it.
- Pre-print sanity checks. Fluidd and the OctoPrint plugin won't let a job start silently: 1.6 g left against 18.4 g required gets you a warning before the nozzle even heats up.
- Per-spool history. When it was bought, opened, dried, how much it printed, which box it lives in.
- Material cost per part. Every spool has a price field, so grams turn into money without a separate spreadsheet.
- One inventory across several printers. A single instance accepts reports from multiple Moonraker and OctoPrint hosts.
What Spoolman actually is
Under the hood it's a Python/FastAPI server, a database and a React web client. By default everything lives in a single SQLite file (spoolman.db), though PostgreSQL, MySQL and CockroachDB are supported too. It all ships as one container: ghcr.io/donkie/spoolman:latest, mirrored on Docker Hub as donkieyo/spoolman:latest.
Here's the part worth internalizing early: Spoolman does not talk to your printer. It has no idea a print is happening. Its whole job is storing data and answering HTTP requests. Something else has to measure the usage and send it over.
| Component | Its role in the chain |
|---|---|
| Klipper | Moves the extruder and exposes the current E axis position in the toolhead object |
| Moonraker | Subscribes to that position, measures the increase in millimeters and pushes it to Spoolman |
| OctoPrint + plugin | Does the same job for Marlin and other non-Klipper firmware |
| Spoolman | Stores spools, converts millimeters into grams, serves the remaining amount over its API |
| Fluidd / Mainsail / KlipperScreen | Display the active spool and let you pick one; none of them has its own spool database |
Development moves fast: v0.26.1 landed on August 7, 2026 with 18 merged changes — per-spool weight overrides, changing a spool's filament or a filament's manufacturer after the fact, and a fix for unsafe JSON parsing in the app settings. Before it came v0.26.0 (July 31, 2026), v0.25.0 (July 22) and v0.24.0 (July 7) — roughly a release every week or two. The web client is translated into 18 languages.
Next to the library sits the dashboard, where spools are grouped by location: "Dry Box 1", "Shelf A", "Prusa MK4S", "Bambu X1C". Drag a card and the spool moves. That's more useful than it sounds — past twenty spools, "where's that gray PETG" stops being a rhetorical question.
What you need before installing
- Any machine on your network that runs Docker: the same Raspberry Pi that runs Klipper, a mini PC, a home server, a NAS. Dedicated hardware is not required.
- A free port — the docs use 7912 everywhere.
- SSH access to the printer or wherever Moonraker lives, so you can edit
moonraker.confandprinter.cfg(Fluidd and Mainsail both have a config editor in the browser). - Your spool data: manufacturer, material, density, diameter, empty spool weight. Most of it comes from the built-in catalog, the rest from the material specs.
- Ten to fifteen minutes for the Docker route, plus the understanding that the database must live on a mounted folder, not inside the container.
Installing Spoolman with Docker, step by step
Docker is the method the official docs recommend: the image carries its own Python and dependencies, so whatever the host has installed doesn't matter.
Step 1: create the data folder and fix its ownership
Inside the container Spoolman runs as UID 1000. Skip this step and the container still starts — it just can't create the database, leaving you with a web UI that forgets everything on restart.
mkdir -p ~/spoolman/data
cd ~/spoolman
chown 1000:1000 dataStep 2: docker-compose.yml
services:
spoolman:
image: ghcr.io/donkie/spoolman:latest
restart: unless-stopped
volumes:
- type: bind
source: ./data # where the data lives on the host
target: /home/app/.local/share/spoolman # do NOT change this line
ports:
- "7912:8000" # host port : container port
environment:
- TZ=Europe/Stockholm # otherwise logs and the UI run on UTC
- PUID=1000
- PGID=1000Inside the container the service always listens on 8000; you can map it to anything outside. The docs and every integration guide use 7912, and it's worth sticking to that — half the community instructions assume it.
Step 3: start it and confirm the database exists
docker compose up -d
docker compose logs --tail 30
ls -la data/ # spoolman.db must show up hereThe UI opens at http://your-host:7912. There's no password and there never will be — more on that in the security section.
Step 4: your first spool
You don't type spools in from scratch. Spoolman pulls a shared catalog called SpoolmanDB — as of August 9, 2026 it holds 6,957 filament entries from 53 manufacturers: Polymaker (1,201 entries), Formfutura (1,056), Extrudr (986), eSUN and Protopasta (315 each), Bambu Lab (269). By material: 3,502 PLA entries, 1,202 PETG, 563 ASA, 529 ABS, 179 TPU. Density, diameter, empty spool weight and temperatures all come from there — exactly the fields the usage math depends on.
The data model has three levels: manufacturer → filament (say "eSUN PLA+ Black", carrying density and temperatures) → spool (one physical reel with its own remaining weight, location and opening date). One filament can have any number of spools, so three black PLA+ reels are three spools of the same filament, each with its own number.
Step 5: updating, and what happens to your data
cd ~/spoolman
docker compose pull && docker compose up -dThe database sits on the mounted folder, so you can swap images freely. The schema migrates automatically the first time a new version boots — but you can't roll back to an older release afterwards, which the docs flag in the install-from-source section.
Installing without Docker: the standalone route
No Docker, and no desire to install it — a common situation on a printer host with limited storage. There's an installer script for that. You'll need curl and unzip.
mkdir -p ./Spoolman && \
curl -s https://api.github.com/repos/Donkie/Spoolman/releases/latest | \
grep -o 'https://[^"]*spoolman.zip' | \
xargs curl -sSL -o temp.zip && \
unzip -o temp.zip -d ./Spoolman && \
rm temp.zip && \
cd ./Spoolman && \
bash ./scripts/install.shThe script writes a .env file with your settings and a systemd unit named Spoolman. The default port here is 7912, not 8000. Management is what you'd expect: sudo systemctl restart Spoolman, sudo systemctl status Spoolman, logs via sudo journalctl -u Spoolman -f. The database lands in ~/.local/share/spoolman/spoolman.db — outside the install folder, so updates never touch it.
The environment variables you'll actually use
Spoolman has no config file format of its own — everything is an environment variable, either in the environment: block for Docker or as KEY=value lines in .env for standalone. The complete list always lives in .env.example in the repo; below are the ones people actually change.
| Variable | Default | What it does |
|---|---|---|
| SPOOLMAN_DB_TYPE | sqlite | Database type: sqlite, postgres, mysql, cockroachdb |
| SPOOLMAN_DB_HOST / _PORT / _NAME | — | External database address. With SQLite, setting _NAME stops the service from starting |
| SPOOLMAN_DB_USERNAME / _PASSWORD | — | Credentials for the external database |
| SPOOLMAN_DB_PASSWORD_FILE | — | Path to a file holding the password, for Docker secrets; takes precedence over the plain password |
| SPOOLMAN_DIR_DATA | ~/.local/share/spoolman | Where the database lives. In Docker this is a path inside the container |
| SPOOLMAN_DIR_BACKUPS | /backups | Where nightly copies go. Defaults to a folder inside the data directory |
| SPOOLMAN_AUTOMATIC_BACKUP | TRUE | Nightly SQLite backup keeping the last 5. No effect on other databases |
| SPOOLMAN_HOST | 0.0.0.0 | Which interface to listen on |
| SPOOLMAN_PORT | 8000 in Docker, 7912 standalone | Listening port. In Docker, remap it in ports: instead |
| SPOOLMAN_BASE_PATH | — | Serve under a sub-path behind a reverse proxy, e.g. /spoolman |
| SPOOLMAN_CORS_ORIGIN | — | Extra browser origins allowed to talk to Spoolman, scheme included: http://mainsail.local:8080 |
| SPOOLMAN_ALLOWED_HOSTS | off | Hostnames this instance answers to — protection against DNS rebinding |
| EXTERNAL_DB_URL | https://donkie.github.io/SpoolmanDB/ | Shared filament catalog URL. An empty value disables the feature |
| EXTERNAL_DB_SYNC_INTERVAL | 3600 | How often to re-fetch the catalog, in seconds. 0 means only at startup |
| SPOOLMAN_METRICS_ENABLED | FALSE | Exposes Prometheus metrics at /metrics |
| SPOOLMAN_LEGACY_CLIENT | FALSE | Falls back to the previous web client if the current one misbehaves |
| SPOOLMAN_LOGGING_LEVEL | INFO | DEBUG also logs every SQL statement — that's what to attach to a bug report |
| PUID / PGID | 1000 / 1000 | User the container runs as. Zero is not allowed |
| TZ | UTC | Timezone for log and UI timestamps |
One more half-hour saver: a typo in a variable name is ignored silently — the setting simply never applies. A typo in a value aborts startup with a message naming the variable. So when an option "does nothing", check the spelling against .env.example before digging deeper. Spoolman lists everything it resolved in the startup log.
Connecting Spoolman to Klipper through Moonraker
Klipper itself knows nothing about Spoolman. Moonraker does all the work through a built-in spoolman component that you enable with three lines of config. Nothing to install separately — unless you're on vendor firmware with a forked Moonraker, which gets its own section below.
Step 1: the [spoolman] section in moonraker.conf
# moonraker.conf
[spoolman]
server: http://192.168.0.123:7912
# URL of the Spoolman instance. Required.
sync_rate: 5
# Sync interval in seconds. Default is 5.Swap 192.168.0.123 for wherever Spoolman runs. On the same Pi as Klipper that's http://localhost:7912. sync_rate controls how often Moonraker flushes accumulated usage: every 5 seconds by default, with 1 as the minimum.
Step 2: the SET_ACTIVE_SPOOL and CLEAR_ACTIVE_SPOOL macros
The component registers a remote method called spoolman_set_active_spool with Klipper. To call it from G-code and macros, add this pair from the Moonraker docs to printer.cfg:
# printer.cfg
[gcode_macro SET_ACTIVE_SPOOL]
gcode:
{% if params.ID %}
{% set id = params.ID|int %}
{action_call_remote_method(
"spoolman_set_active_spool",
spool_id=id
)}
{% else %}
{action_respond_info("Parameter 'ID' is required")}
{% endif %}
[gcode_macro CLEAR_ACTIVE_SPOOL]
gcode:
{action_call_remote_method(
"spoolman_set_active_spool",
spool_id=None
)}After that, SET_ACTIVE_SPOOL ID=1 makes spool #1 the active one and CLEAR_ACTIVE_SPOOL clears the selection — handy when unloading filament so nothing gets charged to the wrong reel. These are the exact same calls that fire when you pick a spool in Fluidd, Mainsail or on the KlipperScreen display; the front ends just invoke the same method.
Step 3: restart and verify the link
# restart Moonraker after editing the config
sudo systemctl restart moonraker
# check the connection
curl -s http://localhost:7125/server/spoolman/status
# the answer looks like this:
# {"result": {"spoolman_connected": true,
# "pending_reports": [],
# "spool_id": 1}}spoolman_connected is the field that matters: Moonraker keeps a persistent WebSocket connection to Spoolman and notices immediately when it disappears. pending_reports holds usage that's been measured but not yet delivered. If the link drops mid-print, the millimeters pile up right there and get flushed once the service returns — a brief network hiccup costs you nothing.
Moonraker exposes two more endpoints alongside status: GET/POST /server/spoolman/spool_id reads or sets the active spool (posting null clears it), and POST /server/spoolman/proxy forwards arbitrary requests to the Spoolman API so front ends don't have to reach a second host directly. The selected spool is stored in Moonraker's own database and survives a restart.
What Fluidd, Mainsail and KlipperScreen give you out of the box
Neither Fluidd nor Mainsail carries a spool database of its own — both lean entirely on Spoolman and the [spoolman] section in Moonraker. Without the service running there's literally nothing to select. Once it's up, the UI picks it up on its own, no plugins involved.
| Feature | Fluidd 1.37 | Mainsail 2.18 | KlipperScreen |
|---|---|---|---|
| Spool picker at print start | Modal, can be disabled in settings | Yes | Yes, dedicated spoolman panel |
| QR code scanning with a webcam | Yes, right in the picker | No | No |
| Active spool card on the dashboard | Yes, with mid-print spool change | Yes | Dedicated Spoolman panel |
| "Is there enough filament" check | Yes | Yes | No |
| Material type matched against the slicer | Yes | Yes | No |
| A separate spool per tool | Yes, via T0/T1 macro variables | Yes, since 2.13.0 | No |
| Selection remembered across restarts | Yes, via [save_variables] | Yes, via [save_variables] | No |
The picker shows up on every print start and can be turned off in Fluidd's settings if you rarely swap spools. Three checks run here: a spool is selected, it holds enough filament for the job, and its material matches what the slicer used. That last one catches the classic "sliced for PETG, loaded PLA" mistake.
Multiple extruders and toolchangers
Time to bury an outdated belief. Until early 2024 multiple simultaneous spools genuinely weren't supported, which is why a popular feature request sat in the Fluidd tracker. It was closed on February 3, 2024, and on February 18 a maintainer posted in the same thread that v1.28.1 shipped the fix. Today every tool gets its own spool.
For tools to appear in that list, your toolchange macros need to declare a spool_id variable and call the assignment:
# printer.cfg
[gcode_macro T0]
variable_spool_id: None
gcode:
...
SET_ACTIVE_SPOOL ID={ printer['gcode_macro T0'].spool_id }
...Mainsail got the same capability later — multi-tool support landed in 2.13.0 (merged December 1, 2024) and works identically: the spool ID lives in a macro variable. A request to redo it "properly" through a dedicated Moonraker API, mapping spools to tools server-side, was closed as not planned on June 29, 2026: the author reported serious pushback on the Moonraker side and called it a non-starter until upstream supports it. So macro variables aren't a temporary hack — they're the current supported approach. Mainsail also handles automated filament changer lanes: its change-spool dialog issues SET_SPOOL_ID LANE=<n> SPOOL_ID=<id>.
Making the selection survive a firmware restart
The active spool lives in Moonraker's database, but per-tool assignments live in macro variables and reset with the firmware. Fluidd and Mainsail both work around that: if either finds a [save_variables] section in your config it saves the selection on every change, and a delayed macro restores it.
# printer.cfg
[delayed_gcode RESTORE_SELECTED_SPOOLS]
initial_duration: 0.1
gcode:
{% set svv = printer.save_variables.variables %}
{% for object in printer %}
{% if object.startswith('gcode_macro ') and printer[object].spool_id is defined %}
{% set macro = object.replace('gcode_macro ', '') %}
{% set var = (macro + '__SPOOL_ID')|lower %}
{% if svv[var] is defined %}
SET_GCODE_VARIABLE MACRO={macro} VARIABLE=spool_id VALUE={svv[var]}
{% endif %}
{% endif %}
{% endfor %}Hooking it up to OctoPrint
On non-Klipper machines running Marlin and friends, OctoPrint plays Moonraker's role. Spoolman's author doesn't ship a plugin, but a third-party one exists and is maintained: Spoolman by Michał Dziekoński, in the OctoPrint catalog since April 27, 2024, AGPLv3. Current version is 1.4.0, released October 18, 2025, and it needs OctoPrint 1.9.0 or newer. The catalog counts at least 538 active installs in the past month — respectable for a niche tool.
Install it through the bundled Plugin Manager or from the archive URL github.com/mdziekon/octoprint-spoolman/archive/master.zip, then point it at your Spoolman instance in the settings.
- Select and deselect spools per tool or extruder — on multi-material setups every tool carries its own spool.
- Filtering, with archived spools hidden by default.
- Pre-print verification: confirm the chosen spool, warn when none is selected, when there isn't enough material for the model, or when the material doesn't match the PrusaSlicer or OrcaSlicer profile.
- HTTPS with a self-signed certificate is supported — you point the plugin at your certificate chain file.
- Retrying usage reports after a failure: up to three attempts on server errors and timeouts.
How Spoolman and Klipper actually interact: the accounting mechanics
This is the section that pays off later, when the numbers don't add up. Here's what happens between Klipper and the database, step by step.
| Stage | What happens |
|---|---|
| 1. Klipper | Drives the extruder; the current E axis position sits in the toolhead object under position |
| 2. Moonraker | Subscribes to toolhead.position and toolhead.extruder, tracking the highest E position seen |
| 3. Moonraker | Accumulates the increase in millimeters for the active spool; switching tools resets the counter to the current position so usage isn't double-counted |
| 4. Moonraker | Every sync_rate seconds sends PUT /api/v1/spool/<id>/use with the body {"use_length": length_in_mm} |
| 5. Spoolman | Converts millimeters into grams using the filament's density and diameter, then reduces the remaining amount |
| 6. Spoolman | Broadcasts the update over WebSocket so every UI refreshes the number without a page reload |
Two consequences follow. First, Moonraker sends length and Spoolman does the gram conversion — so any error in density or diameter turns directly into an error in weight. Second, it counts actual extrusion rather than the slicer's estimate, which means a print cancelled halfway deducts exactly what got pushed through the nozzle.
There's a nice bonus too: Moonraker records the spools used as an auxiliary field in the print history, so a finished job tells you which spool it came off. If you cost out your parts, that's exactly the number missing from a standard print cost calculation.
What to do when the tracked weight drifts from the scale
The official FAQ sets the bar: more than roughly 10% off is worth investigating, less than that is normal spread — filament isn't perfectly uniform and humidity moves the weight around. The troubleshooting order:
- Density and diameter on the filament record. PLA sits around 1.24 g/cm³ and the common diameter is 1.75 mm. A mistake here skews every print at once.
- Extruder E-step calibration. If the printer doesn't push what it thinks it pushes, Spoolman receives a wrong length — that one's not its fault.
- The reporter's logs. Moonraker or OctoPrint will show whether requests went out and what errors came back.
- Spoolman's own logs.
docker compose logsunder Docker,sudo journalctl -u Spoolmanstandalone; the log file also sits next to the database.
Density varies more than people assume, so copying PLA's value everywhere doesn't work: PETG is denser, TPU has its own figure, and carbon-filled blends differ again. The easy path is picking the material from the built-in catalog, where the manufacturer already filled those fields in.
Can you set the spool ID straight from the slicer?
It's the obvious wish: the filament profile already knows what you're printing with, so let the slicer fill in the spool number. There's no supported way to do it, and the discussion thread about it (opened June 29, 2025) still has zero replies.
The person who opened it documented exactly how it falls apart. They put SET_ACTIVE_SPOOL SPOOL_ID=.. in the filament profile's start G-code and called that block as the first line of the machine start G-code. The slicer still injects its own commands ahead of it — EXCLUDE_OBJECT, M73, M106. Worse, when printing a file uploaded through the web interface, Fluidd's spool picker pops up before Klipper has parsed the G-code at all. It's easy to verify: an M118 marker inside the macro reaches the log after the dialog is already on screen. Printing directly from OrcaSlicer, on the other hand, produces no dialog at all.
Spoolman on QIDI Q2, Q1 Pro and other forked-Moonraker firmware
QIDI machines run Klipper, but on vendor firmware with a modified and noticeably older Moonraker. That's why the standard "just add a [spoolman] section" advice hits a wall: the component isn't in the firmware at all. A published walkthrough for the QIDI Q1 Pro on firmware V4.4.24 shows what has to be done by hand — and the same logic applies to the Q2 line built on the same software base.
- SSH into the printer: user
mks, passwordmakerbase; the IP is on the printer screen under Settings → Network. - Drop
spoolman.pyinto/home/mks/moonraker/moonraker/components/— specifically an older revision, because the current upstream one is incompatible with the bundled Moonraker. - Restart the service:
sudo service moonraker restart. - Roll Fluidd back to v1.28.0; newer builds don't come up on this firmware. Keep the original folder as a backup.
- Use Fluidd's config editor to add the
[spoolman]section tomoonraker.confand theSET_ACTIVE_SPOOL/CLEAR_ACTIVE_SPOOLmacros toprinter.cfg. - Power the printer off and on with the physical switch.
One more trick from the same write-up: on QIDI machines filament changes are done from the printer screen through the M603 (unload) and M604 (load) macros, which Spoolman never hears about — so it keeps charging usage to the old spool. The fix is appending CLEAR_ACTIVE_SPOOL to both macros in gcode_macro.cfg, so a filament swap drops the selection and the UI asks you to pick again.
None of this is QIDI-specific. The same story shows up on rooted Anycubic Kobra 3 machines: the Spoolman tracker holds a report from an owner who had to patch the component for the vendor's Klipper fork because the spool-assignment command doesn't exist in that firmware — it was replaced by M555. The general rule is simple: the more a vendor rewrote Moonraker, the more manual work you're signing up for, and the less you should expect anything to "just work".
QR labels: telling three black spools apart
The web client ships with a label designer. You set the size (50×25 mm in the sample), drop a QR code, text fields, a color swatch and rectangles onto the canvas, and fill the text with templates like {filament.name} or {Temp: {filament.nozzleTemp}°C} — the second form disappears entirely when the field is empty. Print on label sheets or a dedicated label printer; designs are stored on the server.
Labels come in two flavors: spool labels, whose QR opens one physical reel, and filament labels, whose QR opens the material and which leave out spool-only fields. The first goes on the reel itself, the second works well on boxes and shelf edges.
It pays off most with materials that look identical but behave differently: silk PLA is indistinguishable from regular PLA by eye, yet its temperatures and bridging behavior aren't the same. A sticker with a color swatch and the nozzle temperature settles the question without opening the UI.
Usage history with Prometheus and Grafana
Spoolman stores the current remaining amount, not a graph of how it got there. If you want to see the plastic melt away hour by hour, set SPOOLMAN_METRICS_ENABLED=TRUE and the service starts exposing Prometheus metrics at /metrics. Prometheus scrapes them, Grafana draws them.
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spoolman'
static_configs:
- targets: ['localhost:7912']This makes sense when you already run Prometheus and Grafana for something else, like monitoring a home server. Standing the whole stack up for a single usage chart is busywork — the same data is right there on the spool card.
Spoolman in Home Assistant
There's no official integration, but a popular community one exists: 237 stars, MIT license, last updated August 8, 2026, so it's very much alive. You add it to HACS as a custom repository; the latest stable release is 1.4.0 from May 10, 2026.
The integration creates a device per spool and per location, and stuffs the attributes with everything: registration date, first and last use, price, density, diameter, remaining weight and length, percentage consumed. There's also an event for crossing a low-stock threshold, which people wire into an automation along the lines of "remind me to reorder when less than 200 g is left".
Bambu Lab: why Spoolman barely helps here
The request for Bambu Lab support has been sitting in the tracker since December 9, 2023 and is still open. It isn't neglect: Klipper has Moonraker, Marlin has OctoPrint, and Bambu's closed firmware offers neither. There is simply nobody to report the usage.
Community workarounds exist and are maintained. One project connects over MQTT, downloads the job's G-code and updates usage layer by layer, matching spools by AMS tray UID (41 stars, updated March 7, 2026). A larger one — 233 stars, updated June 26, 2026, GPL-3.0, with a ready Docker image — keeps AMS trays in sync with Spoolman spools.
Judge them for what they are: add-ons bolted onto a closed ecosystem, dependent on the vendor not changing the protocol. If what you really want is uncompromising control over your Bambu, look at running a self-hosted server for Bambu Lab instead — that problem gets solved a level lower.
When the Pi is enough and when you want a separate server
Let's be blunt: for a single printer at home, Spoolman belongs on the same Raspberry Pi that already runs Klipper. That's the officially supported and by far the most common setup — the service is light, there's no extra network hop, and it costs nothing. The documentation opens with exactly this scenario, and if it describes you, stop here: install it as above and get on with printing.
The simplest way to reach your inventory from outside
If the only thing you're missing is checking remaining spools while standing in a shop, you don't need a server. A private mesh network between your own devices does it: install Tailscale or ZeroTier on the Pi and on your phone, and the phone reaches http://192.168.x.x:7912 as if it were sitting on your couch. No port forwarding, no proxy configuration.
So a mesh network solves exactly one problem — reaching your own Pi. Everything else it leaves untouched, and that's where a separate server starts to make sense:
- Several printers, one shared inventory. Spoolman is built to accept reports from multiple Moonraker and OctoPrint hosts at once. But if it lives on printer number one's Pi, powering that printer down takes the inventory offline for everything else. With printers in different rooms — or different households — a neutral host becomes necessary quickly.
- QR scanning from a phone. Browser camera access requires HTTPS. A certificate needs a real domain name, which means a reverse proxy on a machine that domain can reach.
- Data that outlives the SD card. With Spoolman on the Klipper host, your database and firmware share one card. Card dies, or you reflash for a Klipper upgrade, and two years of inventory go with it.
- Availability while the printer is off. Ordering filament and auditing stock is something you do when there's nothing printing.
- PostgreSQL for several clients and long history. SQLite handles home-scale loads fine, but if you already run Postgres, Spoolman connects to it natively.
Hardware requirements are modest. Spoolman is a light FastAPI service over SQLite — no neural networks, no video transcoding, no heavy rendering. The documentation publishes no minimum CPU or memory figures at all, which is telling in itself: the community runs it on the same single-board computers that host Klipper. In practice, 2 GB of RAM comfortably covers Spoolman plus a reverse proxy with a certificate, and disk space matters more for backups than for the database.
Your printer, in reach from anywhere
Remote access to Klipper and OctoPrint, AI print monitoring and your own model library — on a VPS from 172 ₽ for the first month.
- NVMe drives
- Anti-DDoS
- 24/7 support
- 🇷🇺 🇩🇪 🇳🇱 7 locations
First month with the promo code, then from 429 ₽/mo — Promo plan: 2 GB RAM, 30 GB NVMe, backups included.
Buying via this link supports Printer Hub 🤝
The deployment shape is the same as for the rest of your self-hosted printing stack: the Spoolman container, a reverse proxy with a certificate in front of it, a domain name. If you already run a box for Obico and a model library, Spoolman drops into the same docker-compose.yml as one more service and barely moves the resource needle.
Security: Spoolman has no password
Which leads to the rule the docs call out in a box of its own: if the service is reachable from outside your network, it must sit behind a reverse proxy that handles authentication — Authelia, Authentik, or your proxy's own basic auth. Forwarding a port straight to it is never acceptable, and none of the settings below substitute for that. For how to build that proxy, see our guide on remote access to a 3D printer.
Some things Spoolman does block on its own, with no configuration: writes originating from another website, and WebSocket connections opened from another site — so a page left open in a neighboring tab can't quietly browse your inventory. Requests with no Origin header (Moonraker, OctoPrint, Home Assistant, curl) are unaffected and keep working.
| Setting | When you need it | Format |
|---|---|---|
| SPOOLMAN_CORS_ORIGIN | A browser UI (Fluidd, Mainsail, your own dashboard) lives on a different address | Scheme included: https://fluidd.local,http://mainsail.local:8080. The port is part of the origin; without http:// the rule silently never matches |
| SPOOLMAN_ALLOWED_HOSTS | Protection against DNS rebinding when you reach it by a real domain | Hostnames only, no scheme or port: spoolman.mydomain.com; *.mydomain.com is supported |
| Host header on the proxy | Always, if nginx or Apache is in front | proxy_set_header Host $host; or ProxyPreserveHost On. Traefik, Caddy and HAProxy do it by default |
| SPOOLMAN_CORS_ORIGIN=* | Never on a machine you care about | Disables origin checks completely; Spoolman logs a warning at startup when it's active |
A separate note on metrics: with /metrics enabled, the endpoint serves your vendor and filament names, colors and per-spool prices to anyone who can reach it, unauthenticated. If Prometheus lives outside your network, put that endpoint behind proxy authentication.
Known limitations and what to do about them
Everything below was re-checked in the project tracker on August 9, 2026 — with dates and current statuses, not from memory.
Usage never arrives through an OctoEverywhere tunnel
Reported on November 23, 2025 and still unanswered as of August 9, 2026. The symptom is sneaky: the connection check passes, the Spoolman UI opens through the tunnel, yet usage reports never reach the service — the corresponding call doesn't appear in the logs once during an entire print. Practical conclusion: keep Spoolman on the same network as Moonraker or OctoPrint, and treat the tunnel as a way to look at it, not as transport for accounting.
No Bambu Lab support
Opened December 9, 2023, 38 comments, status unchanged. Native support isn't coming any time soon — it runs straight into closed firmware. The only working options are the third-party bridges described above.
On forked firmware, usage only starts flowing after a restart
Filed January 15, 2025, still open. The owner of a rooted Anycubic Kobra 3 had patched the component for the vendor's Klipper fork; the log shows that after a print starts the wrong kind of request goes out, and only restarting the container brings the pair into a working state. This doesn't affect a stock Klipper and Moonraker pairing, but it does illustrate the price of homemade patches: when they break, you're the one fixing them.
"Spool selection doesn't work in Mainsail"
A classic of the genre: the report has been open since January 1, 2024 with 37 comments, but the last activity was January 7 of that same year. In the thread, one participant rebuilt the setup on a clean MainsailOS with a standalone Spoolman on the same Pi and had it working immediately after updating every component from the Machine tab. Practical conclusion: it's nearly always a version mismatch between Mainsail, Moonraker and Spoolman, or a skipped install step. Update everything and restart Moonraker before hunting for a bug.
And one thing that is not on this list. You'll occasionally see the story about filament being double-counted when a print errors out. That report was indeed filed on January 27, 2025 — and closed by its own author the same day with the comment "orca seems to agree with spoolman spend" — the slicer's own figures matched the deduction. It never held up as a real defect, and the only documented double-counting scenario is retried usage reports on a flaky link in the OctoPrint plugin, which is a checkbox in its settings.
What running Spoolman on your own server actually costs
Spoolman itself is free, MIT-licensed, with no paid tiers or limits of any kind. The only thing you pay for is the box it lives on — and only if you decided to move it off the Pi. Here's what that box actually has to provide:
| Resource | What Spoolman needs | Why |
|---|---|---|
| Memory | 2 GB is comfortable | The service plus a reverse proxy and certificate renewal; 1 GB runs it but leaves no headroom |
| CPU | One core is plenty | FastAPI serving a handful of requests every few seconds |
| Disk | A few GB | The database is tiny; space goes to nightly backups and logs |
| Backups | Included, ideally | The nightly copy lands inside the data folder by default — losing the disk loses both |
| Ports | 80 and 443 | Needed for a real certificate, which the QR scanner depends on |
The practical shopping rule: take the cheapest plan that offers 2 GB of RAM with backups included. A 1 GB plan will run Spoolman, but backups are usually an extra there — and paying separately for them wipes out the saving. Whatever you pick, move the backup directory off the data volume with SPOOLMAN_DIR_BACKUPS, or copy the nightly files somewhere else entirely.
| Before you commit | Why it matters |
|---|---|
| Can you point a domain at it? | No domain, no certificate, no QR scanning from a phone |
| Does the plan include snapshots or backups? | Spoolman's own backup sits next to the database by default |
| Is there an authenticating proxy in front? | Without one, your inventory is world-writable |
| Will every printer reach it? | Moonraker needs a route to the service, not the other way around |
| Is SQLite enough for your load? | Almost certainly yes — and it's the only database with automatic backups |
One honest caveat before you spend anything: a hosted instance is worth it when several printers share the inventory, when you want the QR scanner on your phone, or when you'd rather your spool history didn't live on the same SD card as your firmware. For a single printer none of that applies — scroll back two sections and put Spoolman on the Pi.
Your printer, in reach from anywhere
Remote access to Klipper and OctoPrint, AI print monitoring and your own model library — on a VPS from 172 ₽ for the first month.
- NVMe drives
- Anti-DDoS
- 24/7 support
- 🇷🇺 🇩🇪 🇳🇱 7 locations
First month with the promo code, then from 429 ₽/mo — Promo plan: 2 GB RAM, 30 GB NVMe, backups included.
Buying via this link supports Printer Hub 🤝
Weigh the monthly cost against one long print that stopped three quarters of the way through because the spool ran dry, and the math answers itself. But that's the only argument that holds — convenience alone doesn't justify moving a service off hardware you already own and pay nothing for.
Alternatives to Spoolman
| Option | State | Works with | Verdict |
|---|---|---|---|
| Spoolman | Released August 7, 2026, actively developed | Moonraker/Klipper, OctoPrint, Home Assistant | The de facto standard |
| FilamentManager (OctoPrint plugin) | Last release November 15, 2021 | OctoPrint only | Effectively unmaintained for nearly five years |
| Fluidd / Mainsail | Active | A UI layer over Spoolman | No spool database of their own at all |
| A spreadsheet or a notebook | Eternal | Nothing | Works right up until spool number three |
| Bambu Lab AMS and cloud | Active | Bambu Lab only | Closed ecosystem, unrelated to Spoolman |
FilamentManager deserves a closer look, because older threads still recommend it. The plugin tracks usage by extruded length inside OctoPrint and even supports PostgreSQL for multiple printers. But its last release came out on November 15, 2021, and the upstream repository it was forked from stopped at a 2018 version. There's no live Moonraker synchronization, so a Klipper setup without OctoPrint isn't covered at all. In 2026 there's no reason to install it.
Do these on day one
Frequently asked questions
Sources
- Donkie/Spoolman — the project repository, releases and issue tracker
- Spoolman Wiki — installation, environment variables, backups, security and FAQ
- Moonraker documentation — the
[spoolman]section and spool assignment macros - Fluidd documentation — spool picker, pre-print checks, toolchangers and persisting the selection
- OctoPrint plugin repository — the Spoolman plugin listing, versions and compatibility
- SpoolmanDB — the shared filament catalog behind the density and temperature fields
- Installing Spoolman on a QIDI Q1 Pro — a hands-on write-up on vendor firmware
- Home Assistant integration — spool sensors and low-stock events
Printer Hub Team
We study official documentation and manufacturer guides, test mods on real printers, and analyze community experience from Reddit, Discord, Printables, and YouTube.