Gitignore Generator
Multi-select your stacks and get one merged, deduplicated .gitignore — with every rule explained, not just dumped.
Custom rules stay in this box: they are never stored between visits and never part of a Share link.
Pick stacks on the left — the merged file appears here, every rule explained under the Explained view.
Press ? for shortcuts
Do it offline:$ git check-ignore -v node_modules/left-pad/package.json
How to use it
- Pick your stacks. Press / to jump to the filter, type a few letters — node, uni, mac — and tick what applies. Five groups cover languages, frameworks, editors, operating systems and tooling; each checkbox shows how many rules it contributes.
- Read the result in the output card. Merged file is the artifact itself; Explained lists the same rules one by one with a plain-language reason and the source template for each.
- Add anything project-specific in the custom rules box — one pattern per line, comments allowed. Custom text stays in the box: it is never stored between visits and never part of a Share link.
- Copy the file or download it as .gitignore and drop it in your repository root. Share copies a link carrying only the stack selection and view, so a teammate opens the same setup.
Tick the stacks your repository uses — languages, frameworks, editors, operating systems, tooling — and this page merges them into one deduplicated .gitignore. Nothing is dumped on you unexplained: switch the output card to Explained and every single line says what it ignores, why that belongs out of version control, and which upstream template it came from. Duplicates across stacks are merged once and credited, with the count shown.
How it works
The generator is a curated data pack plus a merge step, all in your browser. Each stack is a short list of rules chosen on purpose — the lines a working repository actually needs — rather than a mirror of everything upstream. Selected stacks are emitted in a fixed catalog order under a comment header per stack, so the same selection always produces byte-identical output, whatever order you clicked in.
Duplicates are resolved by exact pattern text. When two stacks carry the same line — Node and Go both ignore .env — the first section keeps it, a comment above it credits the other sources, and later sections drop it. Only exact matches merge, because near-matches genuinely differ: dist and dist/ do not ignore the same things. The merged count is computed and reported, never guessed.
Every rule row carries provenance. Most patterns are taken verbatim from github/gitignore at one pinned commit (57286c38, CC0-1.0 public-domain dedication), and each names its template; rules for stacks the upstream repository does not cover — Django, Docker, direnv, the Vite conventions — are marked curated. The explanations are this page's own writing, and rules you type in the custom box are explained too, structurally: leading ! means re-include, a trailing slash restricts to directories, a leading **/ matches at any depth, character classes match one listed character.
The claims are verified by execution, not proofreading. This page's test suite writes generated files into a real scratch repository and asks git itself — git check-ignore -v — which rule decides each probe path: node_modules for Node, __pycache__ and .venv for Python, .DS_Store for macOS, target for Rust, *.class for Java, and the negated .env.example, which must stay tracked while .env.local is ignored.
Examples
Node + macOS + VS Codestacks Node · VS Code · macOS
rules 29 unique (1 duplicate merged)
merged .vscode-test — kept in Node, also in VS CodeThe classic laptop setup. The two stacks genuinely share one line — .vscode-test, the downloaded-editors cache used when testing VS Code extensions — so it appears once, in the Node section, with a comment crediting VS Code. The card's counts are computed from the merge, never typed in.
Python + Django# ==== Django ====
db.sqlite3
db.sqlite3-journal
local_settings.py
/media/
/staticfiles/
*.moThe Django section of the merged file. The development database, its journal and the local settings override come from the Django block inside upstream's Python template; /media/ and /staticfiles/ are curated rows for user uploads and collectstatic output, both anchored to the repository root so an app directory named media elsewhere survives.
Node + Go (monorepo)# ==== Go ====
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
go.work
go.work.sumA front-end-plus-service monorepo. Go's section lists nine rules instead of ten because both templates ignore .env — the merge keeps Node's copy, credits Go above it, and reports one duplicate merged. Binary suffixes like *.so stay in the Go section untouched: Node has no exact-match twin for them.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
The last matching rule wins
git evaluates a path against every rule in order and the final match decides, which is why a re-include must come AFTER the exclusion it rescues: .env then !.env.example works, the reverse order ignores the example file too. One hard limit from gitignore(5): a file inside an ignored DIRECTORY cannot be re-included unless the directory itself is rescued first — build/keep.txt stays invisible while build/ is ignored. For the same reason, once your custom block carries a re-include, this generator stops merging your duplicates upward and keeps every custom line exactly where you typed it: with negation in play, the order is the meaning.
Ignoring an already-tracked file changes nothing
.gitignore only filters UNTRACKED files. If node_modules or .env was ever committed, adding the rule leaves git tracking every change as before — the single most-reported "gitignore not working" cause. Untrack it while keeping the copy on disk with git rm --cached <path> (add -r for a directory), commit, and the rule takes over from there.
Three ignore files, three audiences
The repository's .gitignore is shared and versioned — build output, dependency trees. Your global file (git config --global core.excludesFile ~/.gitignore) is for YOUR environment across all repositories — editor swap files, .DS_Store. And .git/info/exclude ignores something in one repository without committing anything — ideal for a scratch directory only you have. This page's editor and OS stacks are prime global-file candidates.
dist and dist/ are different rules
A trailing slash restricts the match to directories: dist/ ignores a folder but not a file named dist, while dist ignores both — and a symlink named dist is only matched by the slash-less form. That is why this generator merges duplicates by exact text only; collapsing near-twins would silently change what your file ignores.
Common errors
Unknown stack id "vue3" — this generator's stacks are fixed ids like node, python or macos; pick from the list.- Almost always a hand-edited share link. Stack ids are frozen full words; open the page fresh and tick the checkboxes instead of guessing ids.
25 stacks selected — the limit for one merged file is 24; deselect 1 to continue.- The cap keeps the merged file reviewable. A repository rarely needs more than a handful of stacks — prefer the global excludes file for editor and OS noise instead of stacking everything here.
Custom rules are 4,097 characters long — the limit is 4,096 characters; trim them before building.- The refusal names the cap and your actual size. If you genuinely carry thousands of custom lines, that block deserves to be its own committed file rather than a paste buffer.
Line 2: a lone "!" negates nothing — put a pattern after it.- A bare exclamation mark is a negation with no pattern to negate. Write the rule it should rescue, for example !keep.txt — and remember it must come after the rule that ignored the file.
Line 1: A control character (U+0007) at position 4 can't appear in a gitignore pattern — file names never contain control characters.- Usually a paste from a terminal that carried an escape or bell character along. The message points at the exact position; retype the line and it passes.
FAQ
Why is my .gitignore not working?
Nine times out of ten the file is already tracked: rules only apply to untracked files, so a committed .env or node_modules keeps updating regardless. Run git rm --cached on the path (with -r for directories), commit, and the rule works from then on. Otherwise check rule order — the last match wins — and use git check-ignore -v <path> to see exactly which line decides.
How do I ignore node_modules in every project?
In one repository, the single line node_modules/ covers the dependency tree at any depth, workspaces included. To ignore it machine-wide instead, put the line in a global excludes file: git config --global core.excludesFile ~/.gitignore, then add the rule there — though for something every clone needs ignored, the shared repository file is the right home.
How do I negate a pattern in .gitignore?
Prefix it with an exclamation mark, placed after the rule it overrides: .env on one line, !.env.example on a later one keeps the sample tracked while real env files stay ignored. Negation cannot reach inside an ignored directory — re-include the directory level first (the pattern pair !build/ then build/* style) or anchor the original rule more narrowly.
How do I set up a global gitignore?
Tell git where it lives: git config --global core.excludesFile ~/.gitignore — the path is yours to choose; modern git also reads ~/.config/git/ignore without any configuration. Put personal noise there: OS artifacts like .DS_Store or Thumbs.db and your editor's swap and cache files. This page's operating-system and editor stacks are exactly that material.
How do I ignore a file only in my local repository?
Add the pattern to .git/info/exclude inside the repository. It behaves like .gitignore but is never committed, so teammates never see it — right for a local scratch folder or a debug script only you keep. For a tracked file you want git to stop watching locally, that is a different mechanism (git update-index --skip-worktree), used sparingly.
Should I commit the .gitignore file itself?
Yes — it is shared configuration, and the repository works differently without it. Commit it at the repository root; subdirectories can carry their own for local build quirks. Keep personal preferences out of it and in your global excludes file instead, so the committed file stays about the project, not about any one machine.
Keyboard shortcuts
- /
- Focus the stack filter
- ⌘/Ctrl + C
- Copy the merged file when no text is selected
- ⌘/Ctrl + Shift + C
- Copy even while text is selected
- ⌘/Ctrl + S
- Download .gitignore
- t
- Switch the theme
- ? or ⌘/Ctrl + /
- Open the shortcut help
- Esc
- Dismiss dialogs and transient state