grab: a Go downloader that refuses to keep sidecar state files
A download manager package for Go
At a glance
- What is it?
- grab downloads many files concurrently with resume, progress, checksums and rate limiting, and it is deliberately stateless: no .crdownload marker beside your partial file, which keeps the filesystem clean and moves the corruption risk onto an assumption that nothing else touches the file. The primary use case is thousands of large immutable files, such as package repositories and ISO libraries.
- Who is it for?
- grab fits Go programs that mirror or fetch large collections of immutable files, since batch concurrency, checksum validation, resumable transfers and pluggable rate limiters are the features the design serves and the v3 module path is stable.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 61 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The use case is thousands of immutable files
The design trade-offs section states the primary use case plainly: concurrently downloading thousands of large files from remote repositories where the remote files are immutable, with operating system package repositories and ISO libraries given as the examples. That is a narrower claim than download manager, and it explains the rest of the design. Thousands of concurrent transfers want goroutines and rate limiting rather than a queue with a progress bar. Immutable remotes mean a partial file can always be resumed rather than restarted, which is what lets the library drop its state files. If your use case is one large file from a source that changes, or a site that needs a browser session, you are outside the shape of the tool.
Stateless by choice, and the assumption that buys it
The interesting trade-off is stated in full. Grab aims to be stateless: the only state that exists is the remote file and the local copy, which may be complete, partially complete or not yet created. The advantage is that the local filesystem is not cluttered with additional state files, and the example given is a .crdownload file. The disadvantage is that grab must make assumptions about the local and remote state, specifically that they have not been modified by another program. The failure mode is stated just as plainly: if the local or remote file is modified outside grab and you download it again with resuming enabled, the local file will likely become corrupted. The suggested remedies are to make remote files immutable or to disable resume. That is a real constraint, and it is why this belongs in a build pipeline rather than in a shared download directory.
Two ways to install: library or command
The module lives at a v3 path, which means the import path carries a major version suffix and your go.mod will show it. To use it inside your own program:
go get github.com/cavaliergopher/grab/v3To get the command line downloader:
go install github.com/cavaliergopher/grab/v3/cmd/grab@latestThe requirement is Go 1.23 or newer. The command is not described at length on this page, which is reasonable: the library is the documented surface and the README's examples are all library code. If you are evaluating this, read the examples rather than the command's flags, because the examples are what tells you whether the API shape suits your program.
grab.Get for the simple case, client.Do for everything else
The minimal example is three meaningful lines: call grab.Get with a destination directory and a URL, handle the error, and print resp.Filename. It downloads the free eBook An Introduction to Programming in Go into the current working directory. The complete example shows why you would use the library instead. You create a client with grab.NewClient, build a request with grab.NewRequest, print req.URL(), then call client.Do to start it and get a response. The design separates the request from its execution, which is what lets one client own concurrency, rate limiting and defaults across many requests. The returned response exposes the underlying HTTP result directly, since the example prints resp.HTTPResponse.Status right after starting.
Progress is read through four accessors and a Done channel
The UI loop in the README is the pattern to copy. A time.Ticker fires every 500 milliseconds, and each tick prints three values from the response: BytesComplete, Size, and Progress multiplied by 100 for a percentage. Completion is signalled through resp.Done, and errors are read afterwards from resp.Err(), which is why the example breaks out of the loop before checking rather than treating Done as success. This is a pull model rather than a callback model: your code decides how often to sample, so you can render a progress bar in a terminal, drive a GUI, or log nothing at all. The second run of the example resumes the previous download and exits sooner, which is the auto-resume behaviour doing its job without appearing in the code.
Interfaces for hashes and rate limiters instead of reimplementations
The stated philosophy is to enable best-in-class functionality for complex features through extensible interfaces rather than reimplementation, and two examples are given. You can provide your own hash algorithm to compute file checksums, which matters if you verify against a format the defaults do not cover, and you can supply your own rate limiter implementation with all the associated trade-offs. So the rate limiter is not an internal heuristic you have to work around. The feature list reinforces the point with concurrent progress monitoring, filename guessing from the content header or the URL path, safe cancellation using context.Context, checksum validation, concurrent batches and rate limiting. Defaults are described as robust and sane, usually derived from the HTTP specifications or from mimicking cURL, wget and common browsers.
The Makefile takes quality gates seriously
make all runs check, which is fmt-check, vet, tidy and test in sequence, and each step fails loudly. Formatting is verified by running go fmt and failing if it rewrote anything, with the message telling you to run make fmt and commit the result. tidy runs go mod tidy and then checks that v3/go.mod and v3/go.sum are unchanged, so an untidy module cannot pass. Tests run with the race detector, coverage, count=1 and shuffle=on, and the same target then builds the command in v3/cmd/grab. The staticcheck pin is worth reading: the Makefile explains that staticcheck reads the export data of the toolchain it runs under, so each release only understands the Go versions it shipped alongside, and pins 2026.2rc1 as the first version that reads Go 1.27. That is a small file making a large statement about how the project maintains itself.
Against net/http or the tools it imitates
For one file, the Go standard library may be enough, while grab says its defaults are usually derived by mimicking cURL, wget and common browsers. If curl already does what you need, curl may be the simpler choice. Where grab earns its keep is the batch case, where you want many transfers running concurrently under one client with shared rate limits, uniform progress reporting and checksum validation, and where you would otherwise be writing the same goroutine plumbing in every place that fetches something. Its stateless design also fits infrastructure, since a worker that is killed and restarted does not have to reconcile a metadata file against a partial download. The cost is the assumption discussed above, and a codebase that shares download directories between processes will hit it.
Editorial conclusion
grab fits Go programs that mirror or fetch large collections of immutable files, since batch concurrency, checksum validation, resumable transfers and pluggable rate limiters are the features the design serves and the v3 module path is stable. Skip it for a single ad hoc file, where net/http or curl already does the job, and skip the resume feature for any file another process might touch, because grab's stateless design means a file modified outside grab while resuming will likely end up corrupted, and the README's own advice is to make remote files immutable or disable resume. Before adopting, read the design trade-offs section in full rather than the feature list, because that is where the real constraints are. The newest state of the project is a pushed main branch with no GitHub releases and a BSD-3-Clause licence.
Frequently asked questions
How do I install the grab download manager?
As a library, run go get github.com/cavaliergopher/grab/v3. To install the command line downloader, run go install github.com/cavaliergopher/grab/v3/cmd/grab@latest. Go 1.23 or newer is required.
Does grab create temporary or partial download files?
No. The library aims to be stateless, so the only state is the remote file and the local copy, and it deliberately avoids sidecar files such as a .crdownload. The README warns that if the file is modified outside grab and you resume, the local file will likely become corrupted, and suggests making remote files immutable or disabling resume.
How do I report progress while downloading with grab?
Read resp.BytesComplete, resp.Size and resp.Progress on a timer, as the README example does with a 500 millisecond ticker, and end the loop when the resp.Done channel fires. Errors are then read from resp.Err(). Downloads also resume automatically on a second run.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cavaliergopher-grab)Was this useful?