Spoolman web client showing the spool library and a spool detail view with 342 g left
The Spoolman web client: spool library on the left, spool details on the right — 342 g left of 1 kg, density 1.24 g/cm³, diameter 1.75 mm

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.

ComponentIts role in the chain
KlipperMoves the extruder and exposes the current E axis position in the toolhead object
MoonrakerSubscribes to that position, measures the increase in millimeters and pushes it to Spoolman
OctoPrint + pluginDoes the same job for Marlin and other non-Klipper firmware
SpoolmanStores spools, converts millimeters into grams, serves the remaining amount over its API
Fluidd / Mainsail / KlipperScreenDisplay the active spool and let you pick one; none of them has its own spool database
Klipper
Its role in the chain: Moves the extruder and exposes the current E axis position in the toolhead object
Moonraker
Its role in the chain: Subscribes to that position, measures the increase in millimeters and pushes it to Spoolman
OctoPrint + plugin
Its role in the chain: Does the same job for Marlin and other non-Klipper firmware
Spoolman
Its role in the chain: Stores spools, converts millimeters into grams, serves the remaining amount over its API
Fluidd / Mainsail / KlipperScreen
Its role in the chain: 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.

Spoolman dashboard with spools as cards grouped by storage location
The dashboard: spools as cards grouped by location — dry boxes, shelves, printers. Drag a card to move a spool somewhere else

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.conf and printer.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.

bash
mkdir -p ~/spoolman/data
cd ~/spoolman
chown 1000:1000 data

Step 2: docker-compose.yml

yaml
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=1000

Inside 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

bash
docker compose up -d
docker compose logs --tail 30
ls -la data/          # spoolman.db must show up here

The 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: manufacturerfilament (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

bash
cd ~/spoolman
docker compose pull && docker compose up -d

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

bash
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.sh

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

VariableDefaultWhat it does
SPOOLMAN_DB_TYPEsqliteDatabase type: sqlite, postgres, mysql, cockroachdb
SPOOLMAN_DB_HOST / _PORT / _NAMEExternal database address. With SQLite, setting _NAME stops the service from starting
SPOOLMAN_DB_USERNAME / _PASSWORDCredentials for the external database
SPOOLMAN_DB_PASSWORD_FILEPath to a file holding the password, for Docker secrets; takes precedence over the plain password
SPOOLMAN_DIR_DATA~/.local/share/spoolmanWhere the database lives. In Docker this is a path inside the container
SPOOLMAN_DIR_BACKUPS/backupsWhere nightly copies go. Defaults to a folder inside the data directory
SPOOLMAN_AUTOMATIC_BACKUPTRUENightly SQLite backup keeping the last 5. No effect on other databases
SPOOLMAN_HOST0.0.0.0Which interface to listen on
SPOOLMAN_PORT8000 in Docker, 7912 standaloneListening port. In Docker, remap it in ports: instead
SPOOLMAN_BASE_PATHServe under a sub-path behind a reverse proxy, e.g. /spoolman
SPOOLMAN_CORS_ORIGINExtra browser origins allowed to talk to Spoolman, scheme included: http://mainsail.local:8080
SPOOLMAN_ALLOWED_HOSTSoffHostnames this instance answers to — protection against DNS rebinding
EXTERNAL_DB_URLhttps://donkie.github.io/SpoolmanDB/Shared filament catalog URL. An empty value disables the feature
EXTERNAL_DB_SYNC_INTERVAL3600How often to re-fetch the catalog, in seconds. 0 means only at startup
SPOOLMAN_METRICS_ENABLEDFALSEExposes Prometheus metrics at /metrics
SPOOLMAN_LEGACY_CLIENTFALSEFalls back to the previous web client if the current one misbehaves
SPOOLMAN_LOGGING_LEVELINFODEBUG also logs every SQL statement — that's what to attach to a bug report
PUID / PGID1000 / 1000User the container runs as. Zero is not allowed
TZUTCTimezone for log and UI timestamps
SPOOLMAN_DB_TYPE
Default: sqlite · What it does: Database type: sqlite, postgres, mysql, cockroachdb
SPOOLMAN_DB_HOST / _PORT / _NAME
Default: · What it does: External database address. With SQLite, setting _NAME stops the service from starting
SPOOLMAN_DB_USERNAME / _PASSWORD
Default: · What it does: Credentials for the external database
SPOOLMAN_DB_PASSWORD_FILE
Default: · What it does: Path to a file holding the password, for Docker secrets; takes precedence over the plain password
SPOOLMAN_DIR_DATA
Default: ~/.local/share/spoolman · What it does: Where the database lives. In Docker this is a path inside the container
SPOOLMAN_DIR_BACKUPS
Default: /backups · What it does: Where nightly copies go. Defaults to a folder inside the data directory
SPOOLMAN_AUTOMATIC_BACKUP
Default: TRUE · What it does: Nightly SQLite backup keeping the last 5. No effect on other databases
SPOOLMAN_HOST
Default: 0.0.0.0 · What it does: Which interface to listen on
SPOOLMAN_PORT
Default: 8000 in Docker, 7912 standalone · What it does: Listening port. In Docker, remap it in ports: instead
SPOOLMAN_BASE_PATH
Default: · What it does: Serve under a sub-path behind a reverse proxy, e.g. /spoolman
SPOOLMAN_CORS_ORIGIN
Default: · What it does: Extra browser origins allowed to talk to Spoolman, scheme included: http://mainsail.local:8080
SPOOLMAN_ALLOWED_HOSTS
Default: off · What it does: Hostnames this instance answers to — protection against DNS rebinding
EXTERNAL_DB_URL
Default: https://donkie.github.io/SpoolmanDB/ · What it does: Shared filament catalog URL. An empty value disables the feature
EXTERNAL_DB_SYNC_INTERVAL
Default: 3600 · What it does: How often to re-fetch the catalog, in seconds. 0 means only at startup
SPOOLMAN_METRICS_ENABLED
Default: FALSE · What it does: Exposes Prometheus metrics at /metrics
SPOOLMAN_LEGACY_CLIENT
Default: FALSE · What it does: Falls back to the previous web client if the current one misbehaves
SPOOLMAN_LOGGING_LEVEL
Default: INFO · What it does: DEBUG also logs every SQL statement — that's what to attach to a bug report
PUID / PGID
Default: 1000 / 1000 · What it does: User the container runs as. Zero is not allowed
TZ
Default: UTC · What it does: 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

ini
# 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:

ini
# 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

bash
# 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.

FeatureFluidd 1.37Mainsail 2.18KlipperScreen
Spool picker at print startModal, can be disabled in settingsYesYes, dedicated spoolman panel
QR code scanning with a webcamYes, right in the pickerNoNo
Active spool card on the dashboardYes, with mid-print spool changeYesDedicated Spoolman panel
"Is there enough filament" checkYesYesNo
Material type matched against the slicerYesYesNo
A separate spool per toolYes, via T0/T1 macro variablesYes, since 2.13.0No
Selection remembered across restartsYes, via [save_variables]Yes, via [save_variables]No
Spool picker at print start
Fluidd 1.37: Modal, can be disabled in settings · Mainsail 2.18: Yes · KlipperScreen: Yes, dedicated spoolman panel
QR code scanning with a webcam
Fluidd 1.37: Yes, right in the picker · Mainsail 2.18: No · KlipperScreen: No
Active spool card on the dashboard
Fluidd 1.37: Yes, with mid-print spool change · Mainsail 2.18: Yes · KlipperScreen: Dedicated Spoolman panel
"Is there enough filament" check
Fluidd 1.37: Yes · Mainsail 2.18: Yes · KlipperScreen: No
Material type matched against the slicer
Fluidd 1.37: Yes · Mainsail 2.18: Yes · KlipperScreen: No
A separate spool per tool
Fluidd 1.37: Yes, via T0/T1 macro variables · Mainsail 2.18: Yes, since 2.13.0 · KlipperScreen: No
Selection remembered across restarts
Fluidd 1.37: Yes, via [save_variables] · Mainsail 2.18: Yes, via [save_variables] · KlipperScreen: No
Fluidd Scan Spool dialog reading a spool QR code through the printer webcam
Fluidd can identify a spool from a QR sticker through the printer's webcam — the window closes itself the moment the code is recognized

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.

Spoolman card on the Fluidd dashboard showing 55 g remaining and a change spool button
The Fluidd dashboard card: vendor, filament, 55 g left of 1 kg and a "Change Spool" button that works mid-print

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.

Fluidd change spool dropdown listing Active Spool, T0 and T1
On a multi-tool machine the dropdown lists T0 and T1 — each gets its own spool, and the standalone "active spool" still exists alongside them

For tools to appear in that list, your toolchange macros need to declare a spool_id variable and call the assignment:

ini
# 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.

ini
# 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.

Spool selection dialog for a tool in the OctoPrint Spoolman plugin
Picking a spool for a specific tool: the list comes from Spoolman with material and remaining weight, plus a shortcut into the web UI
  • 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.
Plugin warning that the selected spool has 1.6 g left while 18.4 g is required
The pre-print check in action: 18.4 g required, 1.6 g left. You can still hit start — but now you know how it ends
OctoPrint settings page for the Spoolman plugin with instance URL and verification options
Plugin settings. Note "Retry spool usage update on error": when the API call fails, the plugin retries the usage report up to three times
OctoPrint sidebar panel listing spools assigned to Tool #0 and Tool #1
The OctoPrint sidebar keeps the per-tool assignments visible at all times

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.

StageWhat happens
1. KlipperDrives the extruder; the current E axis position sits in the toolhead object under position
2. MoonrakerSubscribes to toolhead.position and toolhead.extruder, tracking the highest E position seen
3. MoonrakerAccumulates the increase in millimeters for the active spool; switching tools resets the counter to the current position so usage isn't double-counted
4. MoonrakerEvery sync_rate seconds sends PUT /api/v1/spool/<id>/use with the body {"use_length": length_in_mm}
5. SpoolmanConverts millimeters into grams using the filament's density and diameter, then reduces the remaining amount
6. SpoolmanBroadcasts the update over WebSocket so every UI refreshes the number without a page reload
1. Klipper
What happens: Drives the extruder; the current E axis position sits in the toolhead object under position
2. Moonraker
What happens: Subscribes to toolhead.position and toolhead.extruder, tracking the highest E position seen
3. Moonraker
What happens: 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
What happens: Every sync_rate seconds sends PUT /api/v1/spool/<id>/use with the body {"use_length": length_in_mm}
5. Spoolman
What happens: Converts millimeters into grams using the filament's density and diameter, then reduces the remaining amount
6. Spoolman
What happens: 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:

  1. 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.
  2. 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.
  3. The reporter's logs. Moonraker or OctoPrint will show whether requests went out and what errors came back.
  4. Spoolman's own logs. docker compose logs under Docker, sudo journalctl -u Spoolman standalone; 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.

  1. SSH into the printer: user mks, password makerbase; the IP is on the printer screen under Settings → Network.
  2. Drop spoolman.py into /home/mks/moonraker/moonraker/components/ — specifically an older revision, because the current upstream one is incompatible with the bundled Moonraker.
  3. Restart the service: sudo service moonraker restart.
  4. Roll Fluidd back to v1.28.0; newer builds don't come up on this firmware. Keep the original folder as a backup.
  5. Use Fluidd's config editor to add the [spoolman] section to moonraker.conf and the SET_ACTIVE_SPOOL / CLEAR_ACTIVE_SPOOL macros to printer.cfg.
  6. Power the printer off and on with the physical switch.
Spoolman spool picker inside Fluidd running on a QIDI Q1 Pro
The result on a QIDI Q1 Pro — here Fluidd is opened in OrcaSlicer's Device tab, with the spool list coming from Spoolman

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.

Spoolman spool list showing remaining weight and length for KINGROON and ELEGOO filaments
The same setup from Spoolman's side: remaining weight and length per spool. 43 g used equals 14,077 mm of filament

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.

Spoolman label designer with a 50 by 25 mm label containing a QR code, filament name and color swatch
The label designer: the QR code points at that specific spool, next to the vendor, name, material and a swatch of the actual color

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.

yaml
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'spoolman'
    static_configs:
      - targets: ['localhost:7912']
Grafana chart of spool weight used over time from Spoolman metrics
A Grafana chart: you can see the print itself and the gap between jobs. The axis is grams consumed from one specific spool

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.

Home Assistant sensor card for a Spoolman spool showing the full attribute list
Every spool becomes a device with its own attributes: 564.31 g remaining, 535.69 g used, 48.7% consumed, stored in DryBox

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

Home Assistant dashboard card listing spools with remaining grams
A ready-made dashboard card: the whole inventory with remaining weights, without opening Spoolman at all

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.

Third-party bridge UI mapping Bambu Lab AMS trays to Spoolman spools
A third-party AMS bridge: trays A0–A3 mapped to spools #12–15, with remaining amounts taken from the AMS itself

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.

Printer Hub partner

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
Choose a VPS from 172 ₽

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.

SettingWhen you need itFormat
SPOOLMAN_CORS_ORIGINA browser UI (Fluidd, Mainsail, your own dashboard) lives on a different addressScheme included: https://fluidd.local,http://mainsail.local:8080. The port is part of the origin; without http:// the rule silently never matches
SPOOLMAN_ALLOWED_HOSTSProtection against DNS rebinding when you reach it by a real domainHostnames only, no scheme or port: spoolman.mydomain.com; *.mydomain.com is supported
Host header on the proxyAlways, if nginx or Apache is in frontproxy_set_header Host $host; or ProxyPreserveHost On. Traefik, Caddy and HAProxy do it by default
SPOOLMAN_CORS_ORIGIN=*Never on a machine you care aboutDisables origin checks completely; Spoolman logs a warning at startup when it's active
SPOOLMAN_CORS_ORIGIN
When you need it: A browser UI (Fluidd, Mainsail, your own dashboard) lives on a different address · Format: 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
When you need it: Protection against DNS rebinding when you reach it by a real domain · Format: Hostnames only, no scheme or port: spoolman.mydomain.com; *.mydomain.com is supported
Host header on the proxy
When you need it: Always, if nginx or Apache is in front · Format: proxy_set_header Host $host; or ProxyPreserveHost On. Traefik, Caddy and HAProxy do it by default
SPOOLMAN_CORS_ORIGIN=*
When you need it: Never on a machine you care about · Format: 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:

ResourceWhat Spoolman needsWhy
Memory2 GB is comfortableThe service plus a reverse proxy and certificate renewal; 1 GB runs it but leaves no headroom
CPUOne core is plentyFastAPI serving a handful of requests every few seconds
DiskA few GBThe database is tiny; space goes to nightly backups and logs
BackupsIncluded, ideallyThe nightly copy lands inside the data folder by default — losing the disk loses both
Ports80 and 443Needed for a real certificate, which the QR scanner depends on
Memory
What Spoolman needs: 2 GB is comfortable · Why: The service plus a reverse proxy and certificate renewal; 1 GB runs it but leaves no headroom
CPU
What Spoolman needs: One core is plenty · Why: FastAPI serving a handful of requests every few seconds
Disk
What Spoolman needs: A few GB · Why: The database is tiny; space goes to nightly backups and logs
Backups
What Spoolman needs: Included, ideally · Why: The nightly copy lands inside the data folder by default — losing the disk loses both
Ports
What Spoolman needs: 80 and 443 · Why: 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 commitWhy 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
Can you point a domain at it?
Why it matters: No domain, no certificate, no QR scanning from a phone
Does the plan include snapshots or backups?
Why it matters: Spoolman's own backup sits next to the database by default
Is there an authenticating proxy in front?
Why it matters: Without one, your inventory is world-writable
Will every printer reach it?
Why it matters: Moonraker needs a route to the service, not the other way around
Is SQLite enough for your load?
Why it matters: 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.

Printer Hub partner

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
Choose a VPS from 172 ₽

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

OptionStateWorks withVerdict
SpoolmanReleased August 7, 2026, actively developedMoonraker/Klipper, OctoPrint, Home AssistantThe de facto standard
FilamentManager (OctoPrint plugin)Last release November 15, 2021OctoPrint onlyEffectively unmaintained for nearly five years
Fluidd / MainsailActiveA UI layer over SpoolmanNo spool database of their own at all
A spreadsheet or a notebookEternalNothingWorks right up until spool number three
Bambu Lab AMS and cloudActiveBambu Lab onlyClosed ecosystem, unrelated to Spoolman
Spoolman
State: Released August 7, 2026, actively developed · Works with: Moonraker/Klipper, OctoPrint, Home Assistant · Verdict: The de facto standard
FilamentManager (OctoPrint plugin)
State: Last release November 15, 2021 · Works with: OctoPrint only · Verdict: Effectively unmaintained for nearly five years
Fluidd / Mainsail
State: Active · Works with: A UI layer over Spoolman · Verdict: No spool database of their own at all
A spreadsheet or a notebook
State: Eternal · Works with: Nothing · Verdict: Works right up until spool number three
Bambu Lab AMS and cloud
State: Active · Works with: Bambu Lab only · Verdict: 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

Выполнено: 0 из 8

Frequently asked questions

Sources