It’s often said that Vim plugins make Vim a "real" editor, but the truth is, managing them is where the real magic (and potential pain) lies.

Let’s see it in action. Imagine you’re setting up a fresh Vim instance. You’ve got your .vimrc file, and you want to install a couple of plugins: a fuzzy file finder like fzf.vim and a nice syntax highlighter for a language you’re working with, say, vim-polyglot.

With lazy.nvim, your .vimrc (or more commonly, a separate lua/plugins/init.lua file) might look something like this:

-- ~/.config/nvim/lua/plugins/init.lua
require("lazy").setup({
  -- Plugin 1: fzf.vim
  {
    "junegunn/fzf",
    build = "./install --all", -- Command to run after cloning
    dependencies = { "junegunn/fzf.vim" }, -- fzf.vim depends on fzf
    config = function()
      -- Optional: Configure fzf.vim here
      vim.g.fzf_layout = { width = 0.8, height = 0.7 }
    end,
  },
  "junegunn/fzf.vim", -- The Vim plugin itself

  -- Plugin 2: vim-polyglot
  "sheerun/vim-polyglot",
})

When you restart Neovim, lazy.nvim automatically detects these declarations. It clones the repositories into its managed directory (typically ~/.local/share/lazy/), runs any specified build commands (like ./install --all for fzf), and then sources the plugin code. You’ll see a progress window showing the installation happening.

The problem these plugin managers solve is simple: how to get external code into your editor’s runtime path and ensure it’s loaded correctly, on demand, and without conflicts. Before these tools, you were manually cloning repos into ~/.vim/bundle/ or similar, then manually editing your .vimrc to add them to runtimepath and filetype plugin indent on. It was tedious, error-prone, and a nightmare for keeping things updated or reverting changes.

lazy.nvim (and its predecessors like vim-plug and packer.nvim) centralizes this. You declare what you want, and the manager handles the rest. The "lazy" part of lazy.nvim is key here: it doesn’t load every plugin’s code immediately on startup. Instead, it defers loading until a specific event (like opening a file of a certain type, or running a command) or until you explicitly request it. This drastically speeds up Neovim’s startup time, especially with many plugins.

Here’s how lazy.nvim works internally, conceptually:

  1. Declaration Parsing: It reads your plugin specifications (from Lua tables in your config).
  2. VCS Interaction: It uses Git (or other VCS) commands to clone/pull/update plugins into a dedicated directory.
  3. Dependency Management: It understands that fzf.vim needs fzf to be present and ensures that dependency is met.
  4. Event-Driven Loading: It hooks into Neovim’s event system. When an event occurs that a plugin is configured to respond to (e.g., FileType markdown for a Markdown plugin, or CmdLine for a command-related plugin), lazy.nvim then sources that specific plugin’s code.
  5. Configuration Execution: It runs any config functions you’ve provided for a plugin after it’s loaded.

The exact levers you control are the plugin’s repository URL, its build steps, its dependencies, and crucially, when it should be loaded. For lazy.nvim, this is done through event, cmd, ft (filetype), keys, init, and config parameters in the plugin specification.

For example, to make fzf.vim load only when you type :Files (the command to open fzf for file searching):

{
  "junegunn/fzf.vim",
  cmd = "Files", -- Load only when the 'Files' command is run
  dependencies = { "junegunn/fzf" },
}

This is incredibly powerful for performance. Instead of loading a plugin that might only be used for very specific tasks on every single startup, you defer its loading until the exact moment it’s needed.

The one thing most people don’t realize is how granularly you can control plugin loading to optimize startup. It’s not just about if a plugin loads, but when. For instance, a plugin that provides only syntax highlighting for a rare filetype might be configured to load only when a file of that specific type is opened (ft = "myrarefiletype"), or even better, only when the first buffer of that type is created. This level of deferred loading means that even with hundreds of plugins, your editor can still feel lightning-fast.

The next step after mastering lazy loading is understanding how to manage plugin configuration itself, often involving complex Lua tables and conditional logic.

Want structured learning?

Take the full Vim course →