Configure Neovim – Adding a File Tree Plugin

Published on Mar 5, 2026

Configure Neovim – Adding a File Tree Plugin

Our Neovim setup is coming along nicely. What we are missing though is a way to visually browse the files in our project without having to type :edit paths or reaching for the terminal. That is where a file explorer comes in, and the one we are going to use is neo-tree.nvim.

Neo-tree is a plugin by nvim-neo-tree. It gives you a familiar sidebar panel that lets you browse, open, create, rename, delete and move files and folders, all from inside Neovim. if you have used the file explorer in vs code or any other modern editor, this will feel right at home.


Prerequisites

Before we begin, make sure you have the following already set up from previous posts in this series:

  • lazy.nvim as your plugin manager
  • the folder structure lua/config/ and lua/plugins/ inside your Neovim config directory
  • nvim-tree/nvim-web-devicons — we installed this as a dependency in the statusline post, but if you skipped that, add it now

Neo-tree also requires nerd fonts to display the file and folder icons correctly. if you have not set up a nerd font in your terminal yet, visit Nerdfonts and install one that suit’s you. set it as the font in your terminal emulator settings before continuing.


Installing Neo-tree

In the lua/plugins/ folder, create a new file called neo-tree.lua as we have established in this series, every plugin file must return a table spec for lazy.nvim to read.

-- lua/plugins/neo-tree.lua

return {
  "nvim-neo-tree/neo-tree.nvim",
  branch = "v3.x",
  dependencies = {
    "nvim-lua/plenary.nvim",
    "nvim-tree/nvim-web-devicons",
    "muniftanjim/nui.nvim",
  },
  config = function()
    require("neo-tree").setup()
  end,
}

Save the file, reopen Neovim, and run :lazy sync. lazy.nvim will pick up the new spec and install neo-tree along with it’s dependencies. Once the install completes you can verify it worked by running the command :neotree in Neovim. a sidebar should slide open on the left showing your current working directory.


Understanding the dependencies

You might have noticed that Neo-tree pulls in a few dependencies. Let me explain what each one does so you know why they are there.

  • plenary.nvim is a utility library that many Neovim plugins rely on. It provides helper functions for async operations, file path handling, and more. We have seen it before when setting up git plugins in this series.

  • nvim-web-devicons provides the filetype icons you see next to each file in the tree.

  • nui.nvim is a ui component library for Neovim. Neo-tree uses it to render the sidebar panel and any floating inputs that appear when you rename or create files.


Configuring Neo-tree

The default setup is already quite usable, but we can do better. Let us update the config function to add some options that make the experience more polished.

-- lua/plugins/neo-tree.lua

return {
  "nvim-neo-tree/neo-tree.nvim",
  branch = "v3.x",
  dependencies = {
    "nvim-lua/plenary.nvim",
    "nvim-tree/nvim-web-devicons",
    "muniftanjim/nui.nvim",
  },
  config = function()
    require("neo-tree").setup({
      close_if_last_window = true, -- close neo-tree if it is the last open window
      popup_border_style = "rounded",
      enable_git_status = true,    -- show git status icons next to files
      enable_diagnostics = true,   -- show lsp diagnostic icons

      default_component_configs = {
        indent = {
          indent_size = 2,
          padding = 1,
          with_markers = true,
          indent_marker = "│",
          last_indent_marker = "└",
          highlight = "neotreeindentmarker",
        },
        icon = {
          folder_closed = "",
          folder_open = "",
          folder_empty = "󰜌",
          default = "*",
          highlight = "neotreefileicon",
        },
        modified = {
          symbol = "[+]",
          highlight = "neotreemodified",
        },
        name = {
          trailing_slash = false,
          use_git_status_colors = true,
          highlight = "neotreefilename",
        },
        git_status = {
          symbols = {
            added     = "",
            modified  = "",
            deleted   = "✖",
            renamed   = "󰁕",
            untracked = "",
            ignored   = "",
            unstaged  = "󰄱",
            staged    = "",
            conflict  = "",
          },
        },
      },

      window = {
        position = "left",
        width = 35,
        mapping_options = {
          noremap = true,
          nowait = true,
        },
        mappings = {
          ["<space>"] = {
            "toggle_node",
            nowait = false,
          },
          ["<cr>"] = "open",
          ["l"] = "open",             -- open with vim-style motion
          ["h"] = "close_node",       -- close with vim-style motion
          ["v"] = "open_vsplit",      -- open in vertical split
          ["s"] = "open_split",       -- open in horizontal split
          ["t"] = "open_tabnew",      -- open in new tab
          ["p"] = {
            "toggle_preview",
            config = { use_float = true },
          },
          ["a"] = {
            "add",
            config = { show_path = "none" },
          },
          ["a"] = "add_directory",
          ["d"] = "delete",
          ["r"] = "rename",
          ["y"] = "copy_to_clipboard",
          ["x"] = "cut_to_clipboard",
          ["p"] = "paste_from_clipboard",
          ["c"] = "copy",
          ["m"] = "move",
          ["q"] = "close_window",
          ["r"] = "refresh",
          ["?"] = "show_help",
        },
      },

      filesystem = {
        filtered_items = {
          visible = false,         -- hidden files are hidden by default
          hide_dotfiles = true,
          hide_gitignored = true,
          hide_by_name = {
            "node_modules",
            ".git",
          },
        },
        follow_current_file = {
          enabled = true,          -- highlight the file open in the current buffer
        },
        use_libuv_file_watcher = true, -- auto-refresh on file system changes
      },

      buffers = {
        follow_current_file = {
          enabled = true,
        },
        group_empty_dirs = true,
      },

      git_status = {
        window = {
          position = "float",
        },
      },
    })
  end,
}

There is a lot going on there but most of it is straightforward, let me highlight the parts that matter most.

  • close_if_last_window = true means that if you close all your code buffers and neo-tree is the only window left open, Neovim will exit cleanly rather than leaving you staring at just the file tree.

  • enable_git_status = true each file in the tree gets a small icon showing it’s git status — staged, modified, untracked and so on. It is the same information you would get from git status but visible at all times without running any command.

  • The mappings section inside window gives us vim-style navigation. I have mapped l to open a node and h to close it, which feels very natural if you are used to vim motions. The v and s mappings open a file in a vertical or horizontal split.

  • Under filesystem, setting follow_current_file.enabled = true means the tree will automatically scroll to and highlight whichever file you currently have open in your buffer. This is very useful when you jump to a file with using telescope.nvim and want to see where it sit’s in the folder structure.


Adding a Keymap To Toggle Neo-Tree

At the moment the only way to open neo-tree is by typing :neotree, that is a bit slow. Let us add a keymap so we can toggle it with a quick key combination.

You can add this either directly in the plugin spec using the keys field, or in your lua/config/keymaps.lua file. I prefer the keys field in the spec because it keeps everything in one place.

-- add this inside the plugin spec, at the same level as "config"

keys = {
  {
    "<leader>e",
    "<cmd>neotree toggle<cr>",
    desc = "toggle file explorer",
  },
  {
    "<leader>o",
    "<cmd>neotree focus<cr>",
    desc = "focus file explorer",
  },
  {
    "<leader>ge",
    "<cmd>neotree git_status<cr>",
    desc = "git status in neo-tree",
  },
},

So now pressing <leader>e (spacebar + e) toggles the sidebar open and closed. If the tree is already open and you have moved your cursor into a code buffer, pressing <leader>o will shift focus back to the tree without closing it. <leader>ge opens neo-tree in it’s git status view, which shows all changed files in the project at once.


Once the sidebar is open, navigating it feels very natural if you are comfortable with Neovim. Here is a quick overview of the most useful keys based on the mappings we set up.

  • To open a file, move the cursor to it with j and k and press <cr> (Enter key) or l. To close a folder, press h, to open in a split, press v for a vertical split or s for a horizontal one.

  • To create a new file, press a. Neo-tree will prompt you for a name at the bottom of the screen. If you type a name ending with / it creates a folder instead.

  • To rename something, press r, to delete it, press d — you will be asked to confirm before anything is removed.

  • To toggle hidden files like dotfiles, press H (capital). This is handy when you need to edit a .env or .gitignore file and they are hidden by your hide_dotfiles setting.

  • To see the git status view, press <leader>ge from outside the tree or use the command :neotree git_status. This gives you a focused view of only the files that have changes, which is useful before committing.


Hiding neo-tree from the statusline

One small annoyance with the default setup is that the statusline from lualine (which we set up in the previous post) also renders inside the neo-tree sidebar. We can tell lualine to ignore neo-tree windows by adding a small extension to our lualine config.

Open lua/plugins/lualine.lua and update the options table like this:

-- lua/plugins/lualine.lua

return {
  "nvim-lualine/lualine.nvim",
  dependencies = { "nvim-tree/nvim-web-devicons" },
  config = function()
    require("lualine").setup({
      options = {
        icons_enabled = true,
        theme = "auto",
        -- tell lualine to skip the neo-tree window
        disabled_filetypes = {
          statusline = { "neo-tree" },
        },
      },
    })
  end,
}

With disabled_filetypes set, Lualine will no longer render a statusline inside the neo-tree panel. the sidebar now looks clean and uncluttered.


Tip: Combining With Which-Key

If you have which-key.nvim set up in your config, adding a label for the <leader>e and <leader>o shortcuts helps you remember them when you press <space> and see the popup. add this to your which-key config:

require("which-key").add({
  { "<leader>e", desc = "toggle file explorer" },
  { "<leader>o", desc = "focus file explorer" },
})

Wrapping up

With neo-tree installed and configured, our Neovim setup is really starting to feel like a complete development environment. We now have a file explorer that shows git status at a glance, follows the current file automatically, and can be toggled open and closed in an instant.

To find the rest of my posts on Neovim, click here.

Author Information

aeon501
aeon501

Web Developer, Restless. My mind goes on epic voyages, then return back to reality. I write about things I have experienced in my coding journey.

View all posts
Advertisement
Ad placeholder
Sponsored
Ad 2