How to Download a Folder from GitHub (Without Cloning the Whole Repo)
Open almost any repository on GitHub and you will find a green Code button that offers exactly two things: clone the entire project, or download a ZIP of the entire project. Click into src/components, docs/examples, or a templates folder, and the button is still there — but it still gives you everything. GitHub’s own documentation is blunt about this: you can download a snapshot of a repository’s files, clone it, or fork it. There is no fourth option for a folder.
That gap is why “how to download a folder from GitHub” is one of the most searched GitHub questions, and why the answers you find are so inconsistent. Some still recommend an SVN command that stopped working in January 2024. Others hand you a five-command Git recipe that quietly downloads the whole repository anyway. This guide covers every method that actually works today, what each one costs you, and the specific errors — rate limits, truncated file lists, LFS pointer files — that decide which one is right for your case.
Quick answer: pick your method
| Method | Install required | Private repos | Keeps Git history | Best for |
|---|---|---|---|---|
| Browser folder downloader | No | Yes, with a token | No | One-off downloads, mixed folder sizes |
| Repository ZIP (official) | No | Yes, with a token | No | Small repos where you need most files |
git sparse-checkout |
Git | Yes, with credentials | Yes | You will keep pulling updates |
REST API + curl |
curl, jq | Yes, with a token | No | Scripts, CI, repeatable jobs |
| Copy single files | No | Yes, via API | No | Two or three files from one folder |
If you just want the ZIP, the browser route is the fastest — paste the folder URL into GitDownloader’s homepage tool and it packages only that directory. The rest of this article explains why the other options exist, and when they are the better fit.
Why GitHub has no “download this folder” button
The limitation is not laziness; it falls out of how Git stores data.
A Git repository is a directed graph of objects. Files live in blob objects, and directories are tree objects that list names, modes and hashes pointing at blobs or other trees. A branch is a pointer to one commit, which points to one root tree, which transitively describes the entire snapshot. Nothing in that structure represents “the src/assets folder as an independent, downloadable unit” — a subtree only has meaning inside its parent tree.
Subversion, by contrast, treated directories as first-class check-out targets, which is why the old SVN bridge was the classic workaround. Git’s model gives you history, branching and integrity; the price is that partial retrieval is a client-side problem, not a repository concept.
GitHub’s interface therefore offers what is cheap and unambiguous to serve:
- A ZIP of one ref.
https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zipstreams a snapshot of that branch’s root tree. Useful, but always the whole branch. - The Git Trees API, which can list a subtree in a single request. This is the primitive every folder-download tool is really built on — including ours.
So downloading a folder is not something GitHub does for you. It is something a tool or a script does with GitHub’s API, by listing the files under a path, fetching each one, and zipping the result locally.
Method 1 — Browser-based folder downloader (no install)
This is the shortest path for the majority of people, and the only one that needs nothing installed and no terminal.
- Open the folder on GitHub and make sure the branch selector shows the branch you want.
- Copy the URL from the address bar. It should look like
https://github.com/owner/repo/tree/main/path/to/folder— the/tree/<branch>/<path>shape matters. - Paste it into the URL field on the homepage tool and press Download ZIP.
- The files are listed, fetched and zipped in your browser, then saved to your Downloads folder.
What separates a good tool from a broken one at this step is not the input box — it is what happens next:
- Branches with slashes.
release/2.1is a valid branch name, so a tool must test progressively longer prefixes to work out where the branch ends and the folder path begins, instead of guessing on the first/. - Very large directories. The Trees API returns
"truncated": trueonce a recursive listing exceeds 100,000 entries or 7 MB. A tool that ignores that flag will silently hand you a ZIP with files missing. Correct behavior is to fall back to listing sub-trees one level at a time. - Rate limits. Without authentication you get 60 API requests per hour per IP address; with a token, 5,000. Tools that list directories one-by-one burn through the unauthenticated budget quickly on deep folders, which is why downloads sometimes fail halfway and work again an hour later.
- Git LFS. Repositories using Large File Storage store a tiny pointer file instead of the real asset. If nothing detects that pointer, your ZIP is technically correct and completely useless.
For repositories you own or have access to, paste a fine-grained personal access token in the optional token field: GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → generate one limited to the specific repositories with Contents: Read-only. The token is stored in your own browser and sent only to api.github.com; there is no server in the middle reading your files. More detail is in the FAQ.
Method 2 — The official route: download the whole repository
Still the right answer surprisingly often, and it is worth knowing the direct URLs because they skip the UI entirely.
Through the interface: repository page → Code → Download ZIP. The file arrives named repo-main.zip (or master, or whatever the default branch is).
Direct links you can bookmark or script:
# Branch archive (public repos, no auth)
https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip
# Same file, served by the archive host
https://codeload.github.com/{owner}/{repo}/zip/refs/heads/{branch}
# API zipball — works for private repos when you send a token
https://api.github.com/repos/{owner}/{repo}/zipball/{ref}
Choose this when the repository is small, when you genuinely want most of its files, when you have no Git installed, or when you want the exact commit behind a release tag. The cost is proportional: a monorepo that is 800 MB to clone is roughly 800 MB to ZIP, plus time spent extracting and deleting the 95% you did not need. And a ZIP snapshot is just that — a snapshot. No history, no remote, no git pull.
Method 3 — git sparse-checkout, done properly
When you want the folder plus a working Git checkout — so you can pull updates later — sparse checkout is the right tool. Most tutorials show an outdated recipe; here is the modern one.
git clone --filter=blob:none --sparse https://github.com/owner/repo.git
cd repo
git sparse-checkout set path/to/folder
What matters here:
--filter=blob:nonemakes it a partial clone: Git fetches the commit and tree objects but skips file contents until they are checked out. Without it, you download every blob in the repository and throw most of them away.--sparseinitializes the sparse-checkout file for you, so you do not have to write.git/info/sparse-checkoutby hand.git sparse-checkout setuses cone mode, which matches whole directories and is dramatically faster on large repos. Add more paths later by repeating the command with several paths, and re-rungit sparse-checkout reapplyif the working tree drifts.- Sparse checkout requires Git 2.25 or newer; partial clone (
--filter) requires 2.19+.
The older pattern you will still see in posts and Stack Overflow answers — git init, then git config core.sparseCheckout true, then echo "path/" >> .git/info/sparse-checkout, then git pull origin main — does work, but it downloads the full history and every blob before filtering the working tree. You end up with the folder you wanted plus a .git directory that can easily be several times larger than the files themselves.
The real trade-off: sparse checkout gives you a repository, not an archive. If you wanted a clean ZIP to drop into another project, you now have a checkout with a remote attached — which is exactly what you wanted if you plan to contribute, and pure overhead if you do not.
Method 4 — Script it with the GitHub REST API
For repeatable jobs — vendoring a shared folder into another repo, pulling templates in CI, refreshing docs assets nightly — you want something you can run headlessly. The API gives you two building blocks.
Trees API (one request for the whole subtree):
GET https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1
The response contains a flat tree array with a path and type for every entry. Entries with type: "blob" are files. The catch is the documented ceiling: with recursive=1 the array is capped at 100,000 entries and 7 MB, and once you cross it the response sets "truncated": true. When that happens, the documented fix is to fetch the tree non-recursively and walk sub-trees yourself.
Contents API (one request per directory):
GET https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}
This returns a listing with a download_url for each file, and it is what most browser tools used historically. It never truncates, but it costs one request per directory, which is why deep trees exhaust rate limits.
A complete, small script using the Trees API plus raw.githubusercontent.com:
OWNER=octocat
REPO=Spoon-Knife
REF=main
PREFIX=src/assets
TOKEN="" # set for private repos: export TOKEN=ghp_xxx
AUTH=()
[ -n "$TOKEN" ] && AUTH=(-H "Authorization: Bearer $TOKEN")
# 1. Verify the listing is not truncated before trusting it
curl -s "${AUTH[@]}" \
"https://api.github.com/repos/$OWNER/$REPO/git/trees/$REF?recursive=1" \
| jq -r '.truncated'
# 2. Write every file path under the prefix to a list
curl -s "${AUTH[@]}" \
"https://api.github.com/repos/$OWNER/$REPO/git/trees/$REF?recursive=1" \
| jq -r --arg p "$PREFIX" \
'.tree[] | select(.type == "blob") | select(.path | startswith($p + "/")) | .path' \
> files.txt
# 3. Fetch each file, creating directories as needed
while read -r path; do
mkdir -p "$(dirname "$path")"
curl -sL "${AUTH[@]}" -o "$path" \
"https://raw.githubusercontent.com/$OWNER/$REPO/$REF/$path"
done < files.txt
Two operational notes. First, watch the response headers: X-RateLimit-Remaining tells you how much budget is left and X-RateLimit-Reset when it refills — unauthenticated that budget is 60 requests per hour per IP, so a 200-file folder will fail partway through without a token. Second, raw.githubusercontent.com honors an Authorization header for private repositories, but you must actually send it; without it you get a 404 that looks exactly like a deleted file.
Method 5 — Grab just one file from a folder
Sometimes you do not need the folder at all. Every file has a raw URL that downloads directly:
https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path}
From the GitHub UI, open the file and click Raw (or right-click it and choose “Save link as…”). Adding ?raw=true to a normal /blob/ URL does the same thing. For three files, this beats every tool on this page.
The SVN trick is dead — and tutorials still recommend it
For roughly a decade, the standard advice was to lean on GitHub’s Subversion bridge: replace /tree/main/ with /trunk/ in the URL and run svn checkout or svn export against it, which checks out a single directory without touching Git.
That bridge no longer exists. GitHub announced the sunset of Subversion support in January 2023, ran two brownout periods in November and December 2023 to flush out remaining users, and removed the Subversion protocol entirely on January 8, 2024. GitHub Enterprise Server followed in version 3.13. Also gone: git archive --remote, which needs the server-side upload-archive service that GitHub has never enabled — the command fails with a protocol error no matter how you format the path.
If a guide lists svn checkout as its first method, that guide predates the removal and its other advice deserves scrutiny too. Use sparse checkout, the Trees API, or a browser tool instead.
Which method should you use?
| Method | What you end up with | Handles private repos | Handles LFS | Main cost |
|---|---|---|---|---|
| Browser folder downloader | A ZIP of the folder | Yes, with a fine-grained token | Yes, when the tool fetches LFS media | Needs a correct URL and a tool that respects truncation |
| Repository ZIP | A ZIP of the whole branch | Yes, via the API zipball | Pointers only | Bandwidth and time scale with the repo, not the folder |
git sparse-checkout |
A real Git checkout of the folder | Yes, with credentials | Yes, with Git LFS installed | Not a clean archive; extra Git objects |
REST API + curl |
The exact files you scripted | Yes, with a token | Pointers unless handled | You maintain the script and the rate-limit budget |
| Raw file URLs | Individual files | Yes, with an auth header | Pointers unless handled | Manual and one file at a time |
Troubleshooting: the seven failures you will actually hit
1. A 404 on a repository that clearly exists. It is private and your request is anonymous. Send a token. For anything automated, use a fine-grained token scoped to the specific repos with Contents set to read-only rather than a classic token with broad repo scope.
2. 403 partway through a long download. You hit the API rate limit: 60 requests per hour unauthenticated, 5,000 with a token. The reset time is in the X-RateLimit-Reset header. Authenticate, or use a tool that lists a whole subtree in one request instead of one request per directory.
3. The progress indicator never finishes, or the ZIP is missing files. Classic symptom of a truncated recursive tree listing. Confirm it with "truncated": true in the API response, then fetch sub-trees level by level instead of recursing from the root.
4. Files that are about 130 bytes. Those are Git LFS pointers beginning version https://git-lfs.github.com/spec/v1. The real content lives at https://media.githubusercontent.com/media/{owner}/{repo}/{ref}/{path}. Either clone with Git LFS installed or use a tool that swaps the endpoint automatically.
5. The ZIP does not contain the folder you expected. You are on a different branch than you assumed, or — with the official ZIP — you are looking at the branch root and need to navigate into the folder first. Check the branch selector before copying the URL.
6. Empty folders where a submodule should be. Submodules are separate repositories referenced by a special entry, not files inside the parent. Clone the submodule’s own URL to get its contents.
7. Download blocked or a file silently skipped. Content filters sometimes flag file names, and aggressive ad-blocking or privacy extensions can break parallel downloads. Retry with extensions disabled for the site and check the tool’s status log for the skipped names.
FAQ
Can I download a folder from a private repository? Yes. Every method here supports it, but all of them require authentication: a personal access token with read-only Contents permission for API-based tools, or normal Git credentials for a clone. Never paste a broad-scope token into a third-party website you have not reviewed.
Does downloading a folder preserve Git history? No. ZIP archives and API downloads are snapshots of the current state. Only methods built on git clone — including sparse checkout — keep history.
Why is my download much bigger than the folder I wanted? Because you downloaded the repository ZIP rather than the folder, or because the folder contains large binary assets. Compare against the folder’s own size on GitHub before blaming the tool.
Can I download a folder from a specific branch, tag or commit? Yes. The path segment after /tree/ is the ref, so https://github.com/owner/repo/tree/v2.1.0/path/to/folder works, as does passing a tag or commit SHA to the Trees API.
Is downloading code from GitHub safe? The transport is, but the content is user-uploaded and unreviewed. Check the repository’s license before reusing anything, read the code before you run it, and see the privacy notes in our FAQ for how tokens are handled.
Key takeaways
- GitHub cannot download a single folder because Git’s object model only defines directories as trees inside a snapshot — the folder is a client-side assembly job, not a server feature.
- For a one-off download, a browser tool that respects branch names, truncation, LFS and rate limits will finish the job in seconds without installing anything.
- For work you will keep pulling, use
git clone --filter=blob:none --sparseplusgit sparse-checkout set— not the older.git/info/sparse-checkoutrecipe that downloads everything first. - For automation, the Trees API lists a whole subtree in one request; remember the 100,000-entry ceiling and the 60-versus-5,000 requests-per-hour limit.
- Ignore any guide that still leads with
svn checkout. That path was removed from GitHub on January 8, 2024.
Ready to skip the setup? Paste a folder URL into the GitDownloader tool and it will package just that directory — public or private, LFS included, entirely in your browser.