Ignoring files

Projectile can exclude files and directories from a project in several ways, described below. Which of them apply depends on the indexing method in use - see the indexing method comparison for the full matrix of what each method honors.

Ignoring files using .projectile (a.k.a. dirconfig)

All three indexing methods honor the - ignore and ! unignore entries below. Under alien the ignore entries are handed to the external indexing tool as exclusion arguments (see Indexing). The + keep entries apply everywhere too: under alien they are passed to the indexing tool as path arguments, or applied to its output for the few tools that can’t take them.

If you’d like to instruct Projectile to ignore certain files in a project, when indexing it you can do so in the .projectile file by adding one pattern per line, each preceded with a - sign. The patterns follow gitignore rules and are matched against paths relative to the project root - a leading slash anchors a pattern there.

Lines without any prefix at all are still accepted and treated as ignore patterns for backward compatibility, but the implicit form is being phased out and Projectile now warns about it once per project. Prefer the explicit - prefix in new dirconfigs.

Here’s an example for a typical Rails application:

-/log
-/tmp
-/vendor
-/public/uploads

This would ignore those folders only at the root of the project. Patterns without a slash match at any depth instead:

-tmp
-*.rb
-*.yml
-models

You can also ignore everything except certain subdirectories. This is useful when selecting the directories to keep is easier than selecting the directories to ignore, although you can do both. To select directories to keep, that means everything else will be ignored.

Example:

+/src/foo
+/tests/foo

Keep in mind that you can only include subdirectories, not file patterns.

If both directories to keep and ignore are specified, the directories to keep first apply, restricting what files are considered. The paths and patterns to ignore are then applied to that set.

Finally, you can override ignored files. This is especially useful when some files ignored by your VCS should be considered as part of your project by projectile:

!/src/foo
!*.yml

When a path is overridden, its contents are still subject to ignore patterns. To override those files as well, specify their full path with a bang prefix.

The pattern language

Both ignore examples above are the same thing: gitignore patterns, matched against every file’s root-relative path. There’s one pattern language and one matcher, shared by every indexing method and by the global ignore variables described below.

  • A pattern without a slash matches the file name or any directory segment at any depth: -log ignores a file or directory called log anywhere in the tree, but won’t match xlog or log.txt.

  • A pattern containing a slash is anchored at the project root, and a leading slash anchors without naming a directory of its own: -src/gen and -/log ignore only src/gen and log at the root, not lib/src/gen or src/log. Prefix the pattern with **/ to match at any depth.

  • A trailing slash restricts the pattern to directories: -vendor/ ignores vendor directories, but not files named vendor.

  • A matched directory covers its entire subtree.

  • matches within a single path segment, while * also crosses /; ? matches a single character, and […​]/[!…​] character classes are supported.

  • Matching is case-sensitive.

The search commands (projectile-grep, projectile-ag, projectile-ripgrep, projectile-find-references) apply the very same patterns, translated into whatever exclusion syntax the underlying tool speaks, so a search sees the same set of files indexing does. The one place the translation can’t be exact is grep: in a find path glob crosses /, so a root-anchored pattern like -/.txt also excludes deeper matches there.

Worked examples

The rules compose, and the composition is where people trip up. All three examples below are real: the file lists are what Projectile actually returns.

A monorepo: keep two packages, drop build output

Given this tree:

packages/core/src/a.ts
packages/core/dist/a.js
packages/web/src/b.ts
packages/web/node_modules/dep/i.js
packages/legacy/src/c.ts
node_modules/top/x.js

and this .projectile:

+/packages/core
+/packages/web
-dist/
-node_modules/

you get:

packages/core/src/a.ts
packages/web/src/b.ts

The + entries run first and restrict the project to those two directories, so packages/legacy never enters the picture. The - entries then apply to what’s left, and because neither has a slash they match at any depth: one -dist/ handles every package’s build output, and one -node_modules/ catches both the top-level and the nested one.

Under alien the kept directories are handed to the indexing tool as path arguments (git ls-files — src tests, fd --search-path), so the restriction costs nothing extra. Tools whose command is a shell pipeline can’t take path arguments, so there the listing is filtered inside Emacs instead.

Rescuing a file the ignore rules would drop

Given app/main.rb, config/app.yml, log/dev.log, tmp/cache/x, vendor/assets/lib.min.js and vendor/assets/custom.min.js, plus:

-/log/
-/tmp/
-*.min.js
!/vendor/assets/custom.min.js

you get:

app/main.rb
config/app.yml
vendor/assets/custom.min.js

-*.min.js drops every minified file, and the ! entry pulls one specific file back. This works under all three indexing methods, though a project with ! entries gives up alien’s push-down and is filtered inside Emacs instead - an exclusion handed to `git or fd can’t be taken back afterwards.

Anchored and floating, side by side

The single most common surprise is forgetting that a slash changes the meaning. Given log/a.txt, src/log/b.txt, build/d.o, src/build/c.o and keep.txt:

-/log/
-build/

leaves:

keep.txt
src/log/b.txt

-/log/ is anchored, so it only removes the log directory at the project root and src/log survives. -build/ has no leading slash, so it removes build directories at every depth. Write -log/ if you meant both.

Comments

If you would like to include comment lines in your .projectile file, you can customize the variable projectile-dirconfig-comment-prefix. Assigning it a non-nil character value, e.g. #, will cause lines in the .projectile file whose first non-whitespace character matches that character to be treated as comments instead of patterns.

The same is true of the +, -, and ! prefixes: leading spaces and tabs before the prefix are skipped, so accidental indentation won’t silently turn the entry into a literal ignore pattern.

Ignored files using the project indexing tools

If you’re using the hybrid or alien indexing strategies, the simplest way to ignore some files is just leverage the configuration of the tool you’re using to do the project indexing.

E.g. in the case of git you can just tweak .gitignore.

Sometimes, however, you’d like to have some files as part of your project, but you don’t want to see them in Projectile for whatever reasons.

In those cases the project dirconfig file (.projectile) can be a handy way to further adjust what you want to see in Projectile.

Global ignore and unignore settings

In addition to per-project ignores, Projectile provides several variables for globally ignoring files and directories. Except for projectile-global-ignore-file-patterns (see below) their entries are the gitignore patterns described above, and they take effect with every indexing method; under alien they are pushed down into the external indexing tool.

Out of the box projectile-globally-ignored-directories covers the editor and version control directories plus the dependency and build output directories of the common ecosystems - node_modules, target, pycache, .venv, .next, .terraform and so on. Under alien your VCS is usually ignoring those anyway; the defaults matter for native and hybrid indexing, and for projects that aren’t under version control at all.

Directories that projects do sometimes commit - vendor, dist, public, build - are deliberately left out. If one of the defaults gets in your way, add it to projectile-globally-unignored-directories, or put a ! line in that project’s .projectile to get it back for that project alone.

;; Ignore files by name, at any depth
(setq projectile-globally-ignored-files '("TAGS" "GPATH"))

;; Ignore files by suffix (e.g. compiled artifacts)
(setq projectile-globally-ignored-file-suffixes '(".o" ".pyc" ".elc"))

;; Ignore directories - the entries are patterns, so `*' is a wildcard
(setq projectile-globally-ignored-directories
      '("node_modules"  ; any directory named node_modules, at any depth
        "/tmp"          ; only ./tmp, at the project root
        "*.egg-info"))  ; any directory whose name ends in .egg-info

projectile-global-ignore-file-patterns is the odd one out: its entries are Emacs regexps, matched against absolute file names, not patterns. They can’t be handed to an external tool or expressed as a glob, so they only apply under native indexing.

;; Ignore files matching regexp patterns (native indexing only)
(setq projectile-global-ignore-file-patterns '("\\.min\\.js$" "\\.map$"))
Before Projectile 3.3 a leading in projectile-globally-ignored-directories was a marker meaning "at any depth" rather than a wildcard, and a bare entry was matched at the top level only (under hybrid) or by directory name at any depth (under native). Now every entry is a plain gitignore pattern, matched identically by every indexing method: drop the prefix from entries like *node_modules, and write /tmp if you really mean only the one at the project root.

You can also unignore specific files or directories that would otherwise be excluded. This is useful when your VCS ignores files that you still want Projectile to show:

;; Unignore specific files
(setq projectile-globally-unignored-files '("important.dat"))

;; Unignore specific directories
(setq projectile-globally-unignored-directories '("vendor"))