Tinker AI
Read reviews
beginner 4 min read

.cursorignore: keeping Cursor out of files that should be untouched

Published 2026-05-11 by Owner

Cursor indexes your entire repository when it opens a project. Every file in that index is fair game for Composer suggestions, Tab completions, and context retrieval. That sounds helpful until you realize it includes node_modules, generated migration files, minified vendor bundles, and whatever lives in your .env.local. None of those should influence what Cursor suggests.

.cursorignore is how you fix this. A file at the root of your repo, gitignore-like syntax, scoped to Cursor’s indexer. Files in it don’t show up in Composer’s awareness, don’t pollute Tab completions, and can’t be accidentally suggested for modification by agent mode. It takes about five minutes to set up and the quality difference in completions is noticeable within the first session.

Syntax

.cursorignore uses the same glob patterns as .gitignore. If you already know gitignore, you already know this:

# Ignore a directory and everything under it
node_modules/

# Ignore a specific file extension everywhere
*.min.js

# Ignore a path relative to the repo root
dist/
build/

# Ignore a specific file
.env.local

# Negation (un-ignore) — same as gitignore
!dist/keep-this-file.js

One difference worth noting: .cursorignore doesn’t inherit from parent directories the way git does. Put it at the repo root and list everything from there. Monorepos with package-level gitignores should still manage Cursor exclusions from the root .cursorignore, not per-package files. Cursor reads the root file; per-package files below the root are ignored by the indexer configuration.

The file goes into version control. Like .cursor/rules/, it’s part of the project’s AI configuration and every developer on the team benefits from it being checked in. A .cursorignore that only lives on one machine doesn’t help the next person who clones the repo. Treat it like any other project configuration file — review changes to it in code review, track it in git history, and update it when new generated directories appear in the project.

Obvious entries

These belong in almost every .cursorignore:

# Package manager artifacts
node_modules/
.pnp/

# Build outputs
dist/
build/
out/
.next/
.nuxt/
.astro/

# Coverage and test runner output
coverage/
.nyc_output/

# Source maps and declaration maps
*.js.map
*.d.ts.map

The reason dist/ deserves explicit mention: Cursor doesn’t know that dist/my-component.js is a compiled artifact of src/components/my-component.tsx. It treats both as equally valid sources of truth. If Composer sees both, it might suggest that the src/ file conform to what dist/ looks like — which is the compiled output of an earlier version of the src/ file. That circular reference produces suggestions that are confusing at best and subtly wrong at best. Add dist/ on day one and avoid ever finding out what happens when Composer edits compiled output.

Non-obvious entries

These are the categories people miss:

Database migration files. Tools like Prisma, Drizzle, and Flyway generate migration SQL or TypeScript files in a directory like prisma/migrations/ or drizzle/. These files are append-only by convention — you never hand-edit them, and you should never hand-delete them either. Having Cursor index them means it might suggest completing your new migration based on patterns from old ones. The suggestion can look plausible. It won’t be. Migration files have precise ordering semantics and a corrupt migration history is a serious problem. Keep them out of the index: prisma/migrations/ and drizzle/.

Vendored JavaScript. Some projects copy third-party libraries directly into public/vendor/ or src/vendor/ rather than installing through a package manager. This is a minified bundle from someone else’s codebase, often with different naming conventions, error handling patterns, and comment style than your project. Cursor indexing it produces Tab completions that are stylistically from that vendor’s source, not from yours. Add both public/vendor/ and src/vendor/ to the list.

Large test fixtures. Snapshot tests and integration test fixtures can contain large JSON blobs, HTML dumps, or recorded API responses. A fixture file with 3,000 lines of mock GraphQL response is noise for Tab completions and burns context budget in Composer without providing signal about how your actual code is structured. Add __snapshots__/, tests/fixtures/, and any __fixtures__/ directories.

Secrets and local config files. .cursorignore doesn’t prevent Cursor from reading a file you manually open in the editor, but it does prevent the indexer from proactively pulling it into context. Add .env, .env.local, .env.*.local, *.pem, and *.key. The risk isn’t exfiltration — it’s that your API keys end up in the context window on every suggestion, taking up space that could be used by your actual code.

Auto-generated GraphQL and OpenAPI types. If you run a code generator that produces a src/gql/ or src/__generated__/ directory, that output is derived from your schema. Cursor indexing it leads to suggestions that mix your component logic with the mechanical patterns of generated code. The generated files are correct; they’re just not a model for how humans should write code in the project. Keep them out: src/__generated__/, src/gql/.

How .cursorignore differs from .gitignore

The two files solve different problems, which is why you need both and why their contents should be maintained independently.

.gitignore is about version control: don’t commit build artifacts, don’t commit secrets, don’t track generated files that each developer’s machine will produce independently. The mental model is “what shouldn’t be in the repo.”

.cursorignore is about AI signal quality: don’t let the indexer include files that produce noise, corrupt completions, or provide stylistically misleading context. The mental model is “what shouldn’t influence what Cursor suggests.”

They overlap — node_modules/ belongs in both — but the reasoning is different in each case. .gitignore excludes node_modules/ because it’s enormous and redundant with package.json. .cursorignore excludes it because it contains hundreds of thousands of lines of other people’s code written in other people’s style, and none of that should shape completions in your project.

The distinction matters for files that ARE in your repo but shouldn’t influence Cursor. Generated migration files are checked in (you need them in version history for deploys); they should still be excluded from the indexer. Vendored SDKs are sometimes checked in; Cursor doesn’t need them for context. Lock files like package-lock.json or bun.lockb are checked in for reproducible installs; Cursor has no use for them. .gitignore has no opinion on any of these. .cursorignore has a clear answer: out.

A useful heuristic for borderline cases: if you wouldn’t want Cursor to suggest that you write code in the style of a given file, that file belongs in .cursorignore. The inverse is also worth asking: if Cursor indexed only the files NOT in .cursorignore, would the resulting suggestions be more accurate and more representative of how your team actually writes code? If yes, add the file to the ignore list. The goal is not to hide files from yourself — you can still open and read any ignored file — but to keep the indexer’s picture of the codebase accurate and useful.

A starting template

# Dependencies
node_modules/
.pnp/

# Build outputs
dist/
build/
out/
.next/
.nuxt/
.astro/
.turbo/

# Package manager locks (large, generated, no style signal)
package-lock.json
yarn.lock
bun.lockb
pnpm-lock.yaml

# Database migrations (append-only, never hand-edit)
prisma/migrations/
drizzle/

# Generated types
src/__generated__/
src/gql/
# Test fixtures and snapshots
__snapshots__/
tests/fixtures/
__fixtures__/
# Secrets and local config
.env
.env.local
.env.*.local
*.pem
*.key

# Vendored third-party code
public/vendor/
src/vendor/
# Coverage and instrumentation
coverage/
.nyc_output/

This won’t match every project — a Rails monolith has different directories than a Next.js app — but it covers the categories that cause the most visible problems. Start here and adjust based on what’s in your repo.

One thing worth doing after you first create the file: open a Composer session and check whether the suggestions feel different. The improvement is most obvious in projects that previously had large dist/ directories or vendored dependencies: suggestions stop referencing those files, stay scoped to source code, and are generally more on-style with the rest of the project. If you don’t notice a difference, check that the file is actually at the repo root and not nested inside a subdirectory — that’s the most common reason it doesn’t take effect. You can also verify by checking whether Cursor’s file index (visible in Cursor’s settings under “Indexing”) shows the expected drop in file count after adding entries to .cursorignore. A project with node_modules indexed versus excluded will often differ by tens of thousands of files.

The underlying principle: Cursor’s usefulness scales with the quality of what it indexes. Every file that belongs in the index makes suggestions better. Every file that doesn’t belong there makes them worse. .cursorignore is the control you have over that ratio.