vimtricks.wiki Concise Vim tricks, one at a time.

How do I programmatically find the project root directory in Neovim Lua based on marker files?

Answer

vim.fs.root()

Explanation

vim.fs.root() (Neovim 0.10+) finds a project's root directory by walking up from a starting path and checking for specified marker files or directories. It is the idiomatic Neovim way to implement project-aware features — including custom LSP root_dir functions, per-project settings, and locating config files — without a third-party plugin.

How it works

  • First argument — the starting path (a file path string) or buffer number (0 for current buffer); the search starts from that file's containing directory
  • Second argument — a string, list of strings, or a predicate function; the first ancestor directory that contains any of the listed entries is returned
  • Returns the matched directory path as a string, or nil if none is found

Example

-- Find the root of the project containing the current buffer
local root = vim.fs.root(0, { '.git', 'package.json', 'Makefile', 'pyproject.toml' })
if root then
  print('Project root: ' .. root)
end

-- Use in a custom LSP root_dir function
vim.lsp.config('tsserver', {
  cmd = { 'typescript-language-server', '--stdio' },
  filetypes = { 'typescript', 'javascript' },
  root_markers = { 'tsconfig.json', 'package.json', '.git' },
})

Tips

  • Replaces third-party plugins like vim-rooter for most Neovim 0.10+ use cases
  • Combine with vim.fn.chdir() or :lcd to change the working directory to the root
  • Use vim.fs.find() when you need to locate a specific file (e.g., a config file) rather than just the root directory
  • The root_markers key in vim.lsp.config() uses the same upward-search logic internally

Next

What is the difference between the inner word (iw) and inner WORD (iW) text objects in Vim?