Lazy.nvim is a plugin manager for Neovim that implements deferred loading to minimize startup time. Unlike traditional plugin managers that load all plugins during initialization, Lazy.nvim loads plugins on-demand based on specific triggers like commands, events, or keybindings.
The core innovation is lazy evaluation - plugins remain dormant until actually needed. This approach can reduce Neovim startup time from seconds to milliseconds, even with hundreds of plugins installed. Lazy.nvim achieves this through event-driven loading and dependency resolution that ensures plugins load in the correct order only when required.
Important
By default, all plugins are lazy-loaded in Lazy.nvim unless explicitly marked with
lazy = false. This inverts the traditional plugin management paradigm where eager loading was the default.
Plugin Specification Anatomy
Each plugin in Lazy.nvim is defined by a specification table that describes not just the plugin source, but when and how it should load:
{
"author/plugin-name", -- GitHub repository
lazy = true, -- Default: true
priority = 1000, -- Load order for immediate plugins
event = "BufReadPost", -- Event triggers
cmd = {"Command1", "Command2"}, -- Command triggers
keys = "<leader>f", -- Keymap triggers
ft = {"lua", "python"}, -- Filetype triggers
dependencies = {"dep/plugin"}, -- Plugin dependencies
opts = { setting = true }, -- Configuration table
config = function(_, opts) end, -- Setup function
build = ":TSUpdate", -- Post-install command
enabled = true, -- Can disable plugin
cond = function() return true end -- Conditional loading
}The loading triggers (event, cmd, keys, ft) determine when the plugin becomes active. Without these triggers, plugins load during startup after all lazy plugins, but still later than lazy = false plugins.
Loading Mechanisms
Priority-Based Loading
Priority controls load order for immediate plugins (lazy = false). Higher numbers load first:
-- Colorscheme loads first (needs to be available immediately)
{ "catppuccin/nvim", priority = 1000, lazy = false }
-- Core dependency loads early
{ "nvim-lua/plenary.nvim", priority = 500, lazy = false }
-- Regular plugin (default priority)
{ "nvim-tree/nvim-tree.lua", cmd = "NvimTreeToggle" }Event-Driven Loading
Lazy.nvim hooks into Neovim’s autocmd system to trigger plugin loading:
-- Load when any file is opened
{ "plugin/name", event = "BufReadPost" }
-- Load when entering insert mode
{ "hrsh7th/nvim-cmp", event = "InsertEnter" }
-- Load on multiple events
{ "plugin/name", event = {"BufReadPre", "BufNewFile"} }The plugin remains completely absent from memory until the event fires, then Lazy.nvim loads, configures, and initializes it synchronously.
Command and Keymap Lazy Loading
Command-based loading creates stub commands that trigger plugin loading when executed:
{ "nvim-telescope/telescope.nvim", cmd = "Telescope" }
-- :Telescope command exists immediately but plugin loads on first useKeymap-based loading registers placeholder keymaps that load plugins when pressed:
{
"folke/flash.nvim",
keys = {
{ "<leader>s", mode = {"n", "x"}, desc = "Flash search" },
{ "s", mode = "x", desc = "Flash visual" }
}
}Configuration Patterns
opts vs config Distinction
opts provides the configuration table that Lazy.nvim automatically passes to require("plugin").setup(opts):
{
"nvim-treesitter/nvim-treesitter",
opts = {
highlight = { enable = true },
indent = { enable = true },
ensure_installed = {"lua", "python"}
}
}
-- Equivalent to: require("nvim-treesitter.configs").setup(opts)config provides a custom setup function where you control the entire initialization process:
{
"plugin/name",
config = function(plugin, opts)
require("plugin").setup(opts)
-- Additional setup beyond basic configuration
vim.keymap.set("n", "<leader>x", function()
require("plugin").custom_action()
end)
vim.api.nvim_create_autocmd("BufEnter", {
callback = function()
require("plugin").on_buffer_enter()
end
})
end
}Function-Based Configuration
Configuration can be dynamically generated using functions:
{
"plugin/name",
opts = function()
local config = { basic_setting = true }
if vim.fn.executable("ripgrep") == 1 then
config.use_ripgrep = true
end
return config
end
}This pattern enables environment-aware configuration that adapts based on available tools or system capabilities.
Performance Considerations
Startup Time Impact
Lazy loading dramatically reduces startup time by deferring expensive operations:
- Treesitter parsing only occurs when needed for specific filetypes
- LSP servers start only when editing relevant files
- Large plugin ecosystems (like Telescope extensions) load on-demand
-- Bad: Loads immediately, slowing startup
{ "nvim-treesitter/nvim-treesitter", lazy = false }
-- Good: Loads when editing files
{ "nvim-treesitter/nvim-treesitter", event = "BufReadPost" }Memory Footprint
Unloaded plugins consume zero memory until triggered. A Neovim instance with 200+ plugins might only have 20-30 actually loaded and consuming memory at any given time.
Tip
Use
:Lazy profileto analyze which plugins impact startup time and loading patterns in your configuration.
Advanced Features
Dependency Resolution
Lazy.nvim automatically handles dependency chains and ensures correct load order:
{
"telescope-fzf-native.nvim",
dependencies = {
"nvim-telescope/telescope.nvim", -- Loads first
{ "junegunn/fzf", build = "make" } -- Builds after install
}
}Dependencies load before their dependents, and lazy loading triggers propagate through the dependency chain.
Conditional Plugin Loading
Environment-based conditions enable adaptive configurations:
{
"plugin/windows-only",
cond = function()
return vim.fn.has("win32") == 1
end
}
{
"plugin/work-config",
cond = function()
return vim.fn.getenv("WORK_ENV") == "1"
end
}Build Scripts and Post-Install Actions
The build field executes post-installation commands to compile binaries or update registries:
{ "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" }
{ "telescope-fzf-native.nvim", build = "make" }
{ "williamboman/mason.nvim", build = ":MasonUpdate" }These commands run automatically after plugin installation or updates, ensuring plugins remain properly configured without manual intervention.