← Blog

G-code revisions in a 3D printing workflow

Track which slice printed well: naming conventions, Git, controller stats, or a library that models revisions, plus a worked setup and wasted-filament mistakes.

guidegcodeversioningworkflow

Track G-code revisions by attaching every slice to its source model as a revision, rather than by naming files carefully. A revision-aware library parses the slicer settings out of each file, records which slice printed clean, and puts two revisions side by side when you need to know what changed. Past a handful of parts, that beats filenames, Git, and controller print statistics.

One STL turns into several G-code files faster than most people expect. A bracket gets sliced at 0.2 mm for a quick test, then at 0.16 mm because the top surface looked rough. It snaps under load, so infill goes from 15 to 25 percent in OrcaSlicer. Somebody switches from PLA to PETG and the temperatures change with it. Two of those slices were for the 0.4 mm nozzle and one was for the 0.6 mm machine in the corner.

Three months later the folder holds bracket-final.gcode, bracket-final-v2.gcode, and bracket_FINAL_good.gcode, and none of the names say which nozzle, which material, or which one actually came off the bed straight. Guessing wrong costs a 6 hour print and 80 grams of PETG, and you usually find out four layers from the end.

The problem is not that people are careless with filenames. It is that a filename has room for one short string, while the useful record is a set of settings plus an outcome plus a reason. This guide covers four ways to keep that record, when each one is enough, and a worked setup for the approach that automates most of it.

Four approaches that actually get used

Naming conventions

Encode what matters into the filename and keep it consistent: bracket_016_25pct_petg_0.4_v3_GOOD.gcode. Zero setup, works with every slicer, survives being copied to a USB stick.

It degrades in two predictable ways. The string runs out of room long before the settings do, so something gets dropped, usually the reason for the change. And status inside a filename means renaming files, which breaks whatever pointed at the old name. Good enough for a handful of parts you print monthly. Painful past a few dozen.

Version control

Git is the obvious reach for anyone who writes software. Plain G-code is text, so a diff technically works, and commit messages are the one place a naming convention has no room for.

In practice it fits badly. A 20 MB G-code file per slice makes the repository grow fast, and git-lfs fixes the size but throws away the diff. The diff itself is thousands of coordinate lines where two or three header comments are what you actually wanted to compare. Binary .bgcode from PrusaSlicer is opaque to Git entirely. Version control is a reasonable choice for the slicer profiles and Klipper configs that produce the G-code, and a poor one for the output. The better version of the idea is to make the slice reproducible rather than to archive it, which versioning G-code like software covers with a pinned slicer running in CI.

Controller print statistics

Both major controller stacks already record something, and people forget they have it.

OctoPrint keeps per-file history in its file list: its data model documents prints.success, prints.failure, last.date, last.printTime, and last.success. So the file you uploaded twice, printed once successfully and once badly, says so.

Moonraker’s history component records each job with filename, status, start_time, end_time, print_duration, total_duration, and filament_used, plus job totals, and Mainsail and Fluidd surface it. That is real measured data, not slicer estimates.

The limit is the key. Both track history against a filename on that printer, not against the model the file came from. Delete the file, re-upload it under a new name, or slice a fourth variant, and the connection is gone. Nothing here compares two slices for you, and the record lives on one machine.

A library that models revisions

Purpose-built model libraries treat a sliced file as a revision of its source model, with the settings parsed out of the file automatically and a status you can filter on. PrintStash is the example this guide walks through in detail. GyroidVault takes a similar shape with model versioning, G-code analysis, and print logs, and Manyfold indexes and previews G-code today with version control listed on its roadmap for Q4 2026.

The cost is running a service and keeping it backed up. The gain is that the record survives renames, holds the reason next to the settings, and lives with the model rather than on one printer.

Picking between them

Approach Setup effort Automation Slicer metadata Fleet of printers
Naming conventions None None Whatever fits in the name Works, but nothing is shared
Git or git-lfs Low if you already use Git Commit hooks possible None No printer awareness
Controller statistics Already installed Automatic per job Duration and filament, measured Per machine, no shared view
Revision-aware library A Docker stack to run and back up Automatic on ingest and after prints Parsed from the file One record across machines

There is no wrong answer for a single printer and twenty models. The reason to move up the table is repetition: the same parts, reslices, more than one machine, or more than one person.

What the walkthrough assumes

The rest of this guide implements the fourth approach with PrintStash. You will need:

  • Docker and Docker Compose on an amd64 or arm64 host, with roughly 2 GB of RAM free if you upload large meshes.
  • A slicer that writes metadata comments, which covers OrcaSlicer, PrusaSlicer, Bambu Studio, and Cura.
  • Optionally, a printer reachable on the LAN. Moonraker/Klipper is the stable provider; PrusaLink, OctoPrint, Bambu LAN, and Elegoo Centauri are beta, and the compatibility matrix lists the capability gaps for each.

Nothing here needs a connected printer. Outcomes can be set by hand, and that is the normal starting point.

Setting up the revision record

1. Get the service running

Terminal window
git clone https://github.com/xiao-villamor/PrintStash.git
cd PrintStash
cp .env.example .env
# Set VAULT_JWT_SECRET to a long random string before this is reachable
# from anything but localhost.
docker compose up -d

That pulls prebuilt images rather than building anything. Open http://localhost:3000 and the setup wizard asks for the first admin account, a storage backend, and data directories. There is no default login. SQLite and local disk are the default and best-tested path; Postgres and S3-compatible storage are there when an install outgrows it. The installation guide covers the hardened Compose file and the reverse-proxy variables.

If your models already live on a NAS, add that folder as a shared volume instead of uploading anything. The files stay where they are, get hashed and indexed in place, and new uploads write back into the same tree without overwriting existing paths. Your slicer keeps opening the same paths it always did.

2. Add the source model first, then the slices

Upload the STL, 3MF, OBJ, or STEP source as a model. Then add each G-code or BGCODE file to that model rather than importing it as its own entry. This is the step that decides whether any of the rest works: a slice imported as a separate model is just a differently shaped folder.

On ingest, the file’s contents are hashed, and identical bytes resolve to the model that already holds them instead of starting a second entry. The file itself is still recorded as another version, so a hook that fires twice leaves a duplicate revision to clean up rather than a duplicate model.

3. Let the parser fill in the settings, and write the part it cannot know

Metadata comes out of the file itself. Depending on the slicer and profile, that includes the printer and nozzle profile, layer height, walls, infill, supports, material, temperatures, estimated duration, and filament use. Binary .bgcode is parsed too. Slicers do not all emit the same comments, so missing fields are normal and not worth fighting.

What the parser cannot recover is intent. Add a short label for that: 0.16 finish for visible face, brim after corner lift, 0.6 nozzle for the farm machine. Six months later the label is what tells you why two nearly identical slices both exist.

4. Record outcomes, and keep them separate from your recommendation

Each revision carries one outcome:

  • needs_test for a slice that has not been proven yet;
  • known_good for one that printed successfully;
  • failed for one you do not want repeated;
  • archived for history that should stay out of the way.

Separately, exactly one revision on a model holds the recommended marker. The first G-code you upload claims it automatically, and marking another one clears it everywhere else, so the model page always has exactly one answer rather than zero or four.

Keeping those two fields apart is the point. A fast prototype can be known good while a slower, cleaner revision stays recommended for the part you actually ship. The revision status guide covers the state rules in more depth.

When Auto-mark known good on successful print is enabled, a completed job on a connected printer promotes its own revision. It never overwrites a manual failed or archived verdict, and it does not touch the recommended marker, which stays your decision.

5. Compare revisions instead of reading G-code

Select two revisions and their parsed slicer, material, and print settings appear side by side. This is the step that answers the question you actually have: the second one printed clean, so was it the brim, the 5 degree temperature increase, or the extra wall?

It compares parsed settings, not raw lines. Firmware-specific macros, start G-code, and acceleration tweaks still need a text editor or the slicer when they are the suspect.

6. Connect a printer, if you have one that fits

Add the printer under Printers → Add printer. For Moonraker/Klipper, that is a name and the LAN URL where Mainsail or Fluidd already lives; the status badge should go live within a few seconds.

Smoke test it on a machine where a mistake is harmless: confirm status changes as the printer does, sync the printer’s file inventory, then send a small known-good file without auto-start before you exercise start, pause, resume, and cancel.

After that, sending a revision records the job against that revision. Moonraker returns measured duration and filament use when the print finishes, which is what feeds cost figures. The beta providers still log the job but fall back to slicer estimates. Elegoo Centauri accepts chunked uploads, though upload remains beta and file inventory is disabled. Moonraker can also import a printer’s existing history onto matching models, which backfills prints you ran before any of this was set up.

Removing the upload step

OrcaSlicer’s post-processing hook can push exported G-code straight in. It logs in with a username and a named API key, gets a JWT back, and posts the file to the ingest endpoint, which is the same path the web UI uses. The API key can be revoked from Settings without touching your password.

It saves the upload step but not the filing. Ingest matches by content hash, so each slice arrives as its own library entry rather than a revision of the model it came from, and attaching it with a label and an outcome stays a deliberate action. Point the hook at an inbox collection and file from there. The OrcaSlicer auto-upload guide covers the setup and that limitation, and the API reference has the auth flow and the ingest call.

Mistakes that cost filament

Not marking the outcome while you remember it

A revision left at needs_test after a perfect print is indistinguishable from one that was never tried. Two weeks later you reprint the wrong file, or reslice from scratch because you do not trust any of them. Enabling auto-marking on successful prints removes most of this for connected printers; for manual jobs it is a habit, and the cheapest one on this list.

Reprinting without comparing settings

The failure mode is settings drift you did not notice: a profile update moved the bed temperature, a material swap changed retraction, supports came back on. The part printed fine in March and warps in August, and nothing in the filename changed. Compare the revision you are about to send against the one that worked before committing six hours to it.

Losing which revision produced a job

Controller history is per filename, so uploading bracket.gcode twice from two different slices leaves you with statistics you cannot attribute. Whatever tool you use, make the file identity survive: hash it, version it, or at minimum never reuse a name for different content.

Treating a reslice as a new model

It fragments everything. Two entries for one bracket means two histories, two sets of tags, and no comparison. Add slices to the existing model, always.

Assuming every printer reports the same data back

Measured filament and duration come from Moonraker. Bambu LAN has no remote inventory, Elegoo Centauri’s upload is beta while its inventory stays unavailable, and the beta providers vary in what they confirm. Plan the workflow around the provider you actually own, not the best row in the matrix.

When there is more than one printer

A farm changes the shape of the problem. The same model needs a 0.4 mm nozzle slice and a 0.6 mm slice, and both are legitimately known good for different machines. One of them still holds the recommended marker, so keep the labels explicit about which machine each is for, since the marker cannot express “it depends”.

Queueing G-code across configured printers with manual, default-printer, or least-busy routing shipped in v0.11.0, along with maintenance windows for pulling a machine out of rotation. Routing itself does not score jobs by loaded filament or nozzle, though since v0.12.0 a preflight compares material and nozzle metadata before a direct send and flags a mismatch, so an operator still chooses the machine but is warned when the file does not suit it. The Klipper farm walkthrough covers how this sits alongside Mainsail, and reproducible prints across a fleet covers why an identical file still prints differently on each machine.

Spoolman is worth wiring up once the revision record is real. Selecting a spool per job and letting measured consumption write back gives you actual grams per revision instead of estimates, with double-count protection. It is Moonraker-only, like the rest of the measured data. The Spoolman guide has the setup, and what a print really costs covers turning that into money.

Questions that come up

Should I just use Git for G-code?

Use it for the inputs, not the outputs. Slicer profiles, Klipper configs, and post-processing scripts are small text files that benefit from real diffs and commit messages. G-code is 10 to 30 MB per slice, and a repository of them grows fast; git-lfs solves the size and removes the diff, which was the reason you wanted Git. The diff is also the wrong shape, since thousands of coordinate lines surround the handful of header comments you care about, and binary .bgcode is opaque. A tool that parses the metadata and compares settings answers the actual question faster.

Can known-good status be set automatically?

Yes, for prints sent through a connected printer. With Auto-mark known good on successful print enabled, a job that completes promotes its own revision to known_good. It will not overwrite a verdict you set by hand, so a revision you marked failed or archived stays that way even if a later job on it succeeds. The recommended marker is never set automatically after the first upload, because “printed successfully” and “the one to print next time” are different judgments, and only you can make the second.

What metadata gets extracted from a slice?

Whatever the slicer wrote. For common OrcaSlicer, PrusaSlicer, Bambu Studio, and Cura output that usually covers the printer and nozzle profile, layer height, wall count, infill, supports, material, hotend and bed temperatures, estimated duration, and estimated filament use. Binary .bgcode metadata and thumbnails are read as well, although toolpath preview and printer send are unavailable for it while the compressed body stays undecoded. Fields vary by slicer and profile, so gaps are expected rather than a fault, and a safe sample file is the useful thing to report.

How do I handle one model printed on two different printers?

Keep both slices as revisions on the same model and put the machine in the label, for example 0.6 nozzle, farm A and 0.4 nozzle, desk printer. Both can be known_good at once, since the outcome is per revision. Only one can be recommended, so give that marker to the slice you would send if you had to reprint the part today, and rely on the labels and parsed nozzle size for the rest. Filtering by printer model or revision status finds the right one quickly in a large library.

Does any of this work without a connected printer?

Yes, and it is the normal starting point. Metadata parsing, revisions, labels, notes, outcome states, the recommended marker, and settings comparison are all independent of printers. You set outcomes by hand after each print, which takes a few seconds while you are still holding the part. Connecting a printer adds automatic outcome marking, job history attached to the revision, and measured duration and filament from Moonraker. The record is the valuable part; the automation only removes typing.

Sources

If you are deciding between libraries rather than implementing one, PrintStash vs Manyfold vs STL Shelf compares how three of them handle revisions. If the wider problem is file organization, start with one model, one folder, one history.