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
nilif 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-rooterfor most Neovim 0.10+ use cases - Combine with
vim.fn.chdir()or:lcdto 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_markerskey invim.lsp.config()uses the same upward-search logic internally