A central G-code repository for your print farm
A central G-code repository is one versioned store slices land in and printers read from. How the known-good verdict is recorded, and how files reach machines.
A central G-code repository is the single versioned store your farm sends every slice to, and every printer reads from. Running one takes three things that a shared folder cannot supply on its own: one place slices land, a record on each slice of how it actually printed, and a send path from that store to each machine so nobody walks a USB stick across the room. The folder holds the bytes. It does not know which of the six slices of the same bracket came off the plate straight.
This post uses PrintStash v0.11.4 for the concrete parts, because that is the software I work on. The shape of the problem is the same whichever tool you pick, and two other projects that show up on this question are covered further down.
What the store has to hold besides files
The reason bracket_016_15pct_FINAL_GOOD.gcode exists is that a filename is the only writable field a folder gives you. It breaks the first time FINAL stops being true, and it takes the reason for the change with it. One model, many G-codes works through that in full.
A repository worth calling central attaches each slice to the mesh it came from, keeps the slicer settings it was cut with, and carries one outcome. PrintStash parses OrcaSlicer, PrusaSlicer, Bambu Studio, and Cura output on ingest, including binary .bgcode, so layer height, nozzle, material, temperatures, and the estimated time and filament use come off the file rather than out of the name. Every G-code revision holds one of four statuses: needs_test, known_good, failed, or archived. Separately, exactly one revision per model can be marked recommended, which is the one the model page offers first.
Those two fields answer different questions, and a farm needs both. Known good is history: this file printed. Recommended is a decision: this is the file the next operator should send. A quick prototype slice can be known good and still be the wrong thing to run for a customer part.
Getting slices in without anyone remembering to
A store that only fills up when people remember to upload to it drifts out of date, and then nobody trusts it enough to look there first. The path that needs no discipline is the slicer hook. OrcaSlicer runs a post-processing script on export, and the shipped script pushes the file to POST /api/v1/ingest/orca with an API key, so the slice is in the vault before you have looked away from the plate. The OrcaSlicer auto-upload guide has the setup. Plain uploads go to POST /api/v1/ingest/model (or /api/v1/ingest/url for a source URL).
When you already know which model a slice belongs to, POST /api/v1/models/{model_id}/gcode-revisions takes the file plus revision_label, revision_notes, and an optional is_recommended. Its revision_status defaults to needs_test, which is the right default for a file nobody has printed.
Ingest deduplicates by content hash, and the hash resolves the model. Identical bytes attach to the entry that already holds them instead of creating a second one, so a hook that fires twice costs you a duplicate revision rather than a duplicate model. That matters more in a farm than it sounds, because several people slicing the same part converge on the same record instead of three near-identical library entries.
If the farm’s files already live on a NAS, shared volumes index that folder in place rather than copying it. The feature is opt in and off by default (external_libraries_enabled), and removing a model or a volume never deletes the source bytes. Mirroring a NAS folder covers the sync rules. Worth knowing before you rely on it: a full backup contains the database, managed blobs, thumbnails, and a manifest, but not the source bytes indexed from a shared volume, so the NAS still needs its own backup.
Recording the verdict
Someone has to say whether a print worked, and the cheapest version of that is the software saying it for you. auto_mark_known_good is on by default, so a revision dispatched from the library is marked known good after a successful print. It never overwrites a failed or archived verdict a person set by hand. Prints you started in Mainsail, or history imported from the printer afterwards, are marked known good when the import finishes if auto_mark_known_good is on, because the library now holds them.
Everything else is one call: PATCH /api/v1/models/{model_id}/files/{file_id}/revision updates the status, the notes, or the recommended marker, and marking one file recommended clears the marker from the other G-code files on that model. There is also a batch endpoint for labeling several revisions at once, which is the one you want after importing a folder of old slices.
The payoff for keeping the field populated is filtering. The model list takes revision_status as a filter, so “every part with a known-good slice” is a query instead of a walk through directory listings.
Sending from the store to the machines
There are two ways out of the repository, and which one you use depends on whether you are picking the machine or letting the farm pick it.
Direct send is POST /api/v1/printers/{printer_id}/send with a file_id and start_print, which defaults to false, so upload and start are separate decisions. You can pass a remote_filename and a Spoolman spool_id with it.
The fleet queue is POST /api/v1/fleet/queue, taking a file_id and a strategy of manual, default, or least_busy. Manual requires a printer_id; the other two let the router place the job. One queued job lands on exactly one printer, so running the same part on six machines means queueing six jobs and letting least-busy spread them as machines free up. Sending G-code to multiple printers covers the queue behavior, including reordering, rerouting, and what survives a restart.
What each printer can accept is provider specific, and this is where a mixed farm gets uneven. Moonraker is the stable provider and gets everything: upload, start, remote file inventory, controls, and measured filament and duration back after the job. OctoPrint, PrusaLink, Bambu LAN, and Elegoo Centauri are beta. OctoPrint and PrusaLink upload and expose remote inventory. Bambu LAN in plain-text mode uploads and can start on opt in, with no inventory. Elegoo Centauri takes beta uploads and controls but exposes no inventory, which means the central store can reach it while staying blind to what is already on the machine. Check the compatibility matrix before you plan a workflow around a provider action; it changes with releases.
Access is per printer since v0.11.1. A user holds view, print, control, or admin on each machine independently, and printing also requires edit access to the model’s collection, so a shared repository does not have to mean everyone can start jobs on everything.
What the other tools do with this problem
print-farm-manager (MIT, 178 stars, last commit August 4, 2026) is the project engines cite most often here, and it does hold G-code centrally: uploads go through the web UI into a Docker volume mounted at /app/server/gcode, and a scheduler dispatches them to idle printers, with a human sign-off required before the next job starts. That sign-off is a good idea and PrintStash has no equivalent. What its documentation does not describe is any per-file revision history, outcome, or parsed slicer metadata, so the central part is storage and dispatch rather than a record of what printed well. It also has no authentication and serves printer API keys to any client that can reach it, which is why its own docs restrict it to a trusted LAN.
OctoFarm (AGPL-3.0, 357 stars) still comes up constantly, and its answer to this question was different: it unified multiple OctoPrint instances behind one dashboard and let you manage each printer’s own file system from there. The repository is really each machine’s storage, viewed centrally. The last commit to master landed on May 19, 2023, and it only ever spoke OctoPrint, so a Klipper or Bambu farm gets nothing from it today. The repository is not archived and the README still carries a maintained badge, which is probably why the recommendations keep rolling forward.
When you do not need any of this
One or two printers and one operator: a naming convention and a folder genuinely work, and the overhead of running another service is real. The break point is usually a second person, because that is when the reason for a slice has to be written down instead of remembered.
PrintStash is also not the whole farm. There is no camera wall and no failure detection, so it depends on the printer reporting an outcome rather than noticing spaghetti itself. Routing stays blunt: least-busy does not pick a machine by loaded filament, nozzle, or plate compatibility, though since v0.12.0 a preflight compares material and nozzle metadata before a direct send and flags a mismatch instead of letting an ABS slice land silently on a PLA spool. Orders, quotes, and due dates live outside it entirely. Self-hosted print farm management for Klipper splits those layers up and names the tool for each.
Questions that come up
What makes a central G-code repository different from a shared network folder?
The folder centralises storage and nothing else. A repository centralises the answer to “which file do I send”, which means each slice has to carry the settings it was cut with and the outcome of the last time somebody ran it. In PrintStash that is a revision attached to its source model, with parsed slicer metadata, one of four statuses, and at most one revision per model marked recommended. The practical test is whether a new operator can pick the right file for a part they have never printed without asking anyone. A folder fails that test as soon as it holds two slices of the same thing.
Where should the files physically live, on the NAS or in the app’s storage?
Either works, and the choice is mostly about what already exists. A new farm is simpler with the app owning its storage, because backups then cover the files as well as the database. A farm with years of files already on a NAS should index that folder in place with shared volumes rather than copying it, since removing a model or a volume never touches the source bytes. The catch with the NAS route is that a full backup includes the database, managed blobs, thumbnails, and a manifest, but not source bytes indexed from a shared volume, so you need a separate backup for the NAS.
How do slices get into the repository without someone remembering to upload them?
Through the slicer. OrcaSlicer can run a post-processing script on export, and the shipped script pushes the file to the ingest endpoint using an API key, so the upload happens as part of slicing rather than as a separate chore. Files that arrive some other way can be posted to /api/v1/ingest or added directly as a revision on a known model. Because ingest deduplicates by content hash, a hook that fires twice does not create a second library entry, which removes the main reason people turn automatic uploads off again.
Can every printer in a mixed-brand farm pull from the central store?
Not equally. Moonraker is the stable provider and accepts uploads, starts jobs, exposes remote inventory, and reports measured filament and duration afterwards. OctoPrint and PrusaLink accept uploads and show remote inventory but return no measured filament. Bambu Lab printers in LAN mode accept plain-text uploads and an opt-in start, with no inventory. Elegoo Centauri accepts a beta upload, starts files, and controls the job, but exposes no inventory, so the central store can reach that printer without seeing what is already on it. Confirm the current split on the compatibility page before planning around it, since the beta providers move between releases.
Does the repository need to be reachable from outside the network?
No, and it is easier to defend if it is not. The server has to reach each printer’s address, and everything else can stay on the LAN. Remote access is better handled with a VPN or a hardened reverse proxy with TLS in front of it than by exposing the app. This is also where the farm tools differ sharply: PrintStash has authentication, per-collection roles, and per-printer grants, while print-farm-manager has none and serves printer API keys to anyone who can reach it, which is a real constraint on where you can put it.
Sources
- PrintStash API reference and core concepts. The send and queue payloads, the routing strategies, the four revision statuses, and the
auto_mark_known_gooddefault were read frombackend/app/api/v1/printers.py,backend/app/api/v1/fleet.py,backend/app/api/v1/models.py, andbackend/app/db/models.pyat tag v0.11.4 in the repository. - print-farm-manager README and repository metadata for the G-code volume, the scheduler, the sign-off step, the MIT license, and the August 4, 2026 last commit. Checked August 17, 2026.
- OctoFarm README and repository metadata for the OctoPrint scope, the AGPL-3.0 license, and the May 19, 2023 last commit. Checked August 17, 2026.
- Compatibility matrix for the current per-provider capability split.