← Blog

Sync a printer's SD card with a central library

Two-way SD sync only exists on Klipper, where the card is a server folder. Elsewhere pick a direction of truth: push from the library or pull recorded history.

guideprintersmoonrakerworkflow

Not outside Klipper. A tool that mounts your printer’s SD card over the network and keeps it in step with a central library does not exist for Marlin-era boards, because those boards never expose the card as a filesystem. They answer slow serial requests one file at a time. What exists instead is three workable patterns, and which one fits depends on a decision most people skip: which side is the master. PrintStash covers the push direction natively and the pull direction where a printer actually records history, and this page is explicit about the gap in between.

First decide which side is the master

The word sync hides the real question, which is who holds the truth when the two copies disagree. If the library is the master, models live centrally and printers receive whatever they need to print; nothing sitting on a printer is irreplaceable. If the printer is the master, the files were sliced years ago, copied onto cards by hand or through a phone app, and exist nowhere else; the goal is getting them out before the card dies.

Most people asking this question are in the second state without realizing it, and they assume the first requires solving the second. It does not. You can put new prints under library control today, and backfill old cards once, by hand, at migration time.

What the SD card actually is on each ecosystem

Start with the exception that proves the rule. On a Klipper machine there is no SD card in play at all. Klipper’s virtual_sdcard config points at a folder on the host, Moonraker serves that folder over HTTP, and slicers, frontends, and libraries all read and write it through the same API: list, upload, delete. When people say SD sync genuinely works on Klipper, this is why. The card was always just a directory, and any client can keep a directory in step. PrintStash’s remote file inventory, upload, and delete for Klipper machines sit directly on those endpoints.

Prusa printers running PrusaLink expose their storage over HTTP too, but the storage is physical: an SD slot on older MK3-family machines, internal flash on newer ones. Upload works well across generations. Listing works too, with differences by firmware generation that surface as quirks rather than blockers.

Bambu Lab printers keep their files behind FTPS on the local network, alongside a cache directory holding recent project and G-code artifacts. The files are reachable and the access is clunky, and there is no inventory API to speak of. Treat Bambu storage as a capture source for finished jobs, not a sync partner.

Classic 8-bit and Marlin boards are the hard case, and honesty matters more than optimism here. The firmware exposes the card only through commands like M20, which lists files one serial response at a time. There is no mount, no random access, and no way to read the card while a print is running from it. This is why no honest tool offers automatic SD sync for these machines: the interface was designed to select one file and print it, not to serve a filesystem. Getting files off means pulling the card, or going through OctoPrint’s sdcard view, which travels the same serial link and caches its list until forced to refresh.

Pattern A: the library is master, printers get pushed

If you are starting fresh, or your cards were already cleaned out once, pick this pattern. Models and their G-code revisions live in the central library. Sending a revision to a printer uploads it to that printer’s storage: Moonraker’s gcodes folder, PrusaLink’s local path, OctoPrint’s uploads folder, or Bambu and Elegoo over LAN. From then on the card is a queue, not an archive.

PrintStash implements this push across all five provider types it speaks, and it embeds a marker in each uploaded filename so a later sync can prove which revision a machine is holding instead of guessing by name and size. The compatibility matrix tracks the per-provider details; upload is the one capability all five share today. For farms, the same idea scales up through the fleet queue described in sending G-code to multiple printers, and running a central G-code repository shows what the day-to-day looks like.

Pattern B: pull finished jobs back

This is the half-sync that sounds like what people asked for and only partly is.

On Klipper it works properly. Moonraker keeps a job history on the server, and PrintStash can import it: name a model and a printer and it fetches the most recent hundred entries, matches them against that model’s G-code revisions by filename, and creates print-history rows carrying start time, measured duration, filament used, and cost. If you printed from the printer’s own UI without telling the library, importing pulls the record back in afterwards, and completed imports feed known-good marking where that is enabled. It is an administrator action, and it moves records rather than bytes: the G-code has to be in the library already for a match to land.

On Bambu LAN (beta) the pull is best effort. Prints started outside the library get recorded from the identity fields the printer reports, and while the exact G-code or project 3MF still sits in the printer’s FTPS cache, PrintStash downloads and archives it. Once the cache evicts the file, the history entry stays metadata-only and says so plainly instead of pretending the artifact is on hand.

That is the realistic ceiling for pull: complete metadata plus recoverable artifacts, not a filesystem mirror. On Prusa and OctoPrint machines the pull degenerates into the manual cleanup pass covered in what syncing printer files actually does: download the few strays worth keeping, attach them as revisions, delete the rest.

When to skip the printer entirely

If your slicers already save to a NAS folder, the cleanest answer is to stop routing files through printer storage at all. Mount the share into PrintStash and index it in place: no copies, no import step, and the SD cards shrink to emergency boot media. This composes with both patterns above, since the same library still pushes to printers and imports their histories, and it is the setup we suggest first for single-user shops. See syncing STL files between a PC and a NAS and mirroring a NAS folder without copying files.

Questions that come up

Does PrintStash sync SD cards from any printer brand?

No. It syncs network-reachable storage: inventory, upload, and delete on Moonraker, PrusaLink, and OctoPrint, plus best-effort job capture from Bambu LAN’s FTPS cache. Classic Marlin SD cards are outside anyone’s honest reach over the network, ours included. If a page promises generic SD-card auto-sync for those boards, check the claim against how Marlin actually exposes files before believing it.

Is two-way sync possible between a library and a printer?

Effectively yes on Klipper, because both sides see the same folder through Moonraker’s file API, and a change made on either side is visible to both. For closed ecosystems, no. Pick a direction of truth instead, library-master push or job-history pull, and leave the other direction manual.

Can OctoPrint copy files off the printer’s SD card?

It can list what is on the card and manage single files over the firmware’s serial interface, which is slow and cached until refreshed. It is a rescue hatch for finding one file, not a mechanism for mirroring a card. Bulk copy-off-the-card tools do not exist because the serial protocol was never built for it.

What about the files already stuck on my printer’s card?

Copy them off once during migration: pull the card, or grab the keepers through the printer’s own web UI. Ingest them into the library attached to the right models, then stop treating the card as storage. One afternoon of this turns an ongoing sync problem into settled history.

Sources

  • Moonraker file management API for list, upload, and delete on the gcodes root, and the Klipper virtual_sdcard config. Checked August 22, 2026.
  • Marlin M20 and the surrounding file-management G-code for how classic boards expose the card. Checked August 22, 2026.
  • OctoPrint files API for the local and sdcard origins and the refresh-on-demand cache. Checked August 22, 2026.
  • PrintStash provider behavior read from backend/app/services/job_import.py, backend/app/services/printer_hub.py (_capture_external_artifact), backend/packages/printstash-core/src/printstash_core/printers/moonraker.py, and docs/provider-support.md at tag v0.12.1 in the repository.
  • The compatibility matrix for the current per-provider capability split.