DeveloperUtilityProductivity

Gitignore Generator

Generate a correct .gitignore from real github/gitignore templates. Pick languages, frameworks, editors, and OS, merge duplicate patterns automatically, then copy or download the file.

Starter presets

Matches template names and the patterns inside them.

Selected (4)

Languages & Runtimes

Frameworks

Editors & IDEs

Operating Systems

Build & Tooling

Generated .gitignore

Templates

4

Rules

84

Lines

174

Duplicates merged

4

# .gitignore generated by toolsflare.com/gitignore-generator
# Templates: Node, Next.js, Visual Studio Code, macOS

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Dependency directories
node_modules/
jspm_packages/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache
.cache
.parcel-cache

# Build output
dist/
build/
out/

# Next.js build output
.next/

# Nuxt.js build / generate output
.nuxt
.output

# Gatsby files
.cache/
public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Next.js ###
# Next.js build output

# Production build

# Vercel
.vercel

# Next.js env files
.env*.local

# TypeScript
next-env.d.ts

# Turbopack
.turbo/

### Visual Studio Code ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

How .gitignore pattern syntax actually works

A .gitignore file is a list of glob patterns, one per line, evaluated against paths relative to the directory containing the file. Git only consults these patterns for untracked files — that distinction is the source of almost every "my .gitignore isn't working" report.

The matching engine is fnmatch with FNM_PATHNAME: a plain * matches any run of characters except a slash, so *.log matches app.log at any depth but src/*.log matches only one level below src/.

  • Leading slash anchors: /build matches only build in the same directory as the .gitignore. Without the slash, build matches build at any depth, including vendor/lib/build.
  • A slash anywhere in the middle also anchors: doc/frotz behaves like /doc/frotz. Only a pattern with no slash at all (or one solely at the end) floats to any depth.
  • Trailing slash = directories only: build/ ignores the directory build and everything under it, but never a regular file named build.
  • ** crosses directory boundaries: **/foo matches foo anywhere; abc/** matches everything inside abc; a/**/b matches a/b, a/x/b, a/x/y/b.
  • ? and […]: ? matches exactly one non-slash character; [Dd]esktop.ini and *.py[cod] are character classes, not regex alternation.
  • ! negates: !important.log re-includes a file an earlier pattern excluded. Later lines win over earlier ones.
  • # starts a comment, and a blank line matches nothing (useful as a separator). Escape a literal leading # or ! with a backslash: \#notacomment, \!important. A trailing space is stripped unless escaped as \ .

The negation gotcha: it is impossible to re-include a file if a parent directory of that file is excluded. Git never descends into an ignored directory, so it never sees the file to reconsider it.

logs/ then !logs/keep.txt does not work. Exclude the contents instead of the directory: logs/* followed by !logs/keep.txt. For a deeper path you must un-ignore every intermediate level: /a/*, !/a/b/, /a/b/*, !/a/b/c.txt.

.gitignore does not untrack files Git already knows about

This is the single most common .gitignore problem. Ignore rules are consulted only when Git decides whether to show an untracked path. Once a file is in the index — because someone committed it before the rule existed, or added it with git add -f — adding a pattern changes nothing. The file keeps showing up in git status and keeps getting committed.

The fix is to remove it from the index while leaving it on disk. The --cached flag is what keeps your working copy intact:

# One specific file or directory
git rm --cached .env
git rm -r --cached node_modules/

# Or re-apply every ignore rule to the whole repo at once
git rm -r --cached .
git add .
git commit -m "Apply .gitignore"

git rm -r --cached . empties the index, then git add . refills it — and this time the ignore rules apply, so the ignored paths are left out. The resulting commit shows the ignored files as deletions, which is expected: they are deleted from the repository, not from your disk. Commit any real work first, because staged changes are rewritten by this sequence.

To check why a specific path is or is not ignored, ask Git directly: git check-ignore -v path/to/file prints the exact file, line number, and pattern responsible. If it prints nothing, no rule matches and the file is tracked or simply not covered.

A secret that was ever committed is still in history. git rm --cached only removes it going forward. Anyone with the repo can read it from an older commit. Rotate the credential, then rewrite history with git filter-repo or BFG if the repo was published.

Which ignore file wins: precedence order

Git reads ignore patterns from several sources. For a given path it takes the highest-precedence source that has any matching pattern, and within that source the last matching line decides. Sources are listed here from highest to lowest precedence.

PrecedenceSourceScopeCommitted?
1 (highest)Command line (-e)Single commandNo
2.gitignore (deepest dir)That directory and below; nested files override parentsYes — shared with the team
3.git/info/excludeThis clone onlyNo — never leaves your machine
4 (lowest)core.excludesFileEvery repo for this userNo — global config

Because nested .gitignore files sit above the repo-root one, a src/.gitignore containing !bundle.js re-includes a file that the root file ignored — as long as no parent directory is itself excluded.

Rule of thumb: put patterns everyone on the project needs (build output, dependency directories, secrets) in a committed .gitignore. Put your personal editor and OS junk in the global file so you do not force it on contributors:

git config --global core.excludesFile ~/.gitignore_global
printf '.DS_Store\n.idea/\n*.swp\n' >> ~/.gitignore_global

Common patterns and what they match

PatternMatchesDoes not match
*.loga.log, src/x/b.loglog, logs/a.txt
build/build/, app/build/out.jsbuild (a file)
/buildbuild at repo rootapp/build
doc/*.txtdoc/a.txtdoc/sub/a.txt
doc/**/*.txtdoc/a.txt, doc/x/y/a.txta.txt
**/node_modules/node_modules/, pkg/a/node_modules/node_modules.bak
.env*.env, .env.local, .env.exampleenv, config/.env
.env*
!.env.example
.env, .env.local.env.example (re-included)
*.py[cod]a.pyc, a.pyo, a.pyda.py
[Dd]esktop.iniDesktop.ini, desktop.iniDESKTOP.INI
logs/*
!logs/.keep
logs/app.loglogs/.keep (kept, so the dir survives)

Git cannot track empty directories, which is why projects commit a placeholder such as .keep or .gitkeep and un-ignore it with !. The name has no special meaning to Git — it is a convention.

Why merging duplicates matters

Stacking templates produces a lot of repetition. Node, React, Vue, and Next.js all ship dist/ and .env.local; macOS, Ruby, and JetBrains templates each contribute their own overlapping junk. A naive concatenation of six templates commonly repeats 40–60 patterns.

Duplicates are harmless to Git — matching the same path twice changes nothing — but they make the file hard to audit, and they hide the one line that actually matters when you are debugging why a path is ignored. This generator keeps the first occurrence in a stable order, drops the rest, and reports the count so you can see how much overlap your stack has. Turn the toggle off if you prefer each ### Template ### block to stay verbatim and self-contained.

One thing dedupe deliberately does not do is reorder lines. Order is semantically meaningful in Git: a later ! negation overrides an earlier exclusion within the same file. Templates are emitted in a fixed sequence and lines keep their relative position, so a template's exclude/re-include pairs — like Yarn's .yarn/* followed by !.yarn/releases — still behave correctly.