Delegates all git clones, checkouts, and package handling directly to vim.pack. Zero custom wrapper weight on startup.
Uses nvim-pack-lock.json to guarantee 100% reproducible plugin states across machines with single-command rollback.
opts & main Shorthand
Simplify configurations! Passing opts = {} automatically calls require(main).setup(opts) without verbose boilerplate.
Discovers pre-existing disk plugins and adopts them seamlessly on startup tagged as (native).
pack.nvim requires Neovim 0.12+ for vim.pack native APIs. On older Neovim versions, require('pack').setup() issues a friendly warning and gracefully exits.
-- 1. Enable Neovim's built-in bytecode cache (must be on Line 1 for full benefits)
vim.loader.enable()
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- 2. Bootstrap pack.nvim using Neovim's native vim.pack
vim.pack.add({ { src = "https://github.com/igmrrf/pack.nvim", branch = "main" } })
vim.cmd.packadd("pack.nvim")
-- 3. Initialize pack.nvim with options and plugin specs
require("pack").setup({
performance = {
vim_loader = true, -- Fallback caching check
},
ui = {
border = "rounded", -- Options: "single", "double", "rounded", "solid", "shadow"
auto_open = true, -- Auto-open float when uninstalled plugins exist
silent = nil, -- Silences native messages (defaults to auto_open setting)
filter = "default", -- Options: "default" (vim.ui.input), "input", or fun(opts, cb)
icons = {
loaded = "●",
not_loaded = "○",
error = "✖",
sync = "↺",
},
},
plugins = {
{ "igmrrf/pack.nvim" }, -- Self-management
{ "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },
{ "neovim/nvim-lspconfig", lazy = true, event = "BufReadPre" },
{ "folke/tokyonight.nvim", opts = { style = "night" } },
{ import = "plugins" }, -- Import specs from lua/plugins/*.lua
},
})
require("configs")
-- Generated spec will appear here
Toggling lazy = true/false automatically migrates the plugin directory between opt/ and start/ seamlessly without manual intervention.
Select multiple plugins with <Space> in the dashboard to perform batch update, disable, clean, or delete operations cleanly.
Run :Pack profile to render visual ASCII startup timelines breaking down exact load durations for every plugin.
{
"neovim/nvim-lspconfig",
lazy = true,
event = { "BufReadPre", "BufNewFile" },
ft = { "lua", "python", "rust" },
cmd = "LspInfo",
keys = {
{ "gd", "<cmd>lua vim.lsp.buf.definition()<cr>", desc = "Go to Definition" },
},
opts = {
servers = { lua_ls = {} },
},
}
nvim-pack-lock.json)
Native vim.pack tracks commit revisions and branch pins directly. Use :Pack restore to roll back your installation to the exact lockfile state across any machine.
nvim-pack-extra.json)
Disabling plugins via x in the dashboard persists state into nvim-pack-extra.json in your data directory, maintaining clean declarative lua configs.
Execute :Pack repair to realign lockfile revisions to installed plugin HEAD commits whenever manual disk edits or custom checkouts occur.
pack.nvim queries vim.pack for unmanaged disk plugins. Unmanaged plugins appear in the :Pack dashboard marked with a (native) tag and function normally!
-- To upgrade an adopted disk plugin into a fully managed spec with opts/lazy triggers:
require("pack").setup({
plugins = {
-- Already cloned on disk by vim.pack.add() -> pack.nvim adopts & configures it
{ "nvim-lua/plenary.nvim" },
{ "folke/trouble.nvim", opts = {} },
}
})
| Hook Type | Example Spec | Behavior |
|---|---|---|
| Shell Command | build = "make" or build = "cargo build --release" |
Executes in the plugin's root folder post-install or update. |
| Ex-Command | build = ":TSUpdate" or build = ":Helptags" |
Executes as a Vim command after loading. |
| Lua Function | build = function(plugin) ... end |
Receives the plugin object context for custom execution logic. |
{
"nvim-telescope/telescope.nvim",
dependencies = {
"nvim-lua/plenary.nvim",
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
},
config = function(plugin, opts)
local telescope = require("telescope")
telescope.setup(opts)
telescope.load_extension("fzf")
end,
}
| Feature / Key | lazy.nvim | pack.nvim | Migration Notes |
|---|---|---|---|
| Minimum Neovim | 0.8+ | 0.12+ | Requires Neovim 0.12 native vim.pack. |
| Bootstrap Snippet | Manual git clone to stdpath("data")/lazy/lazy.nvim |
Native vim.pack.add(...) |
Zero manual directory manipulation or rtp prepending. |
| Setup Signature | require("lazy").setup({ specs }) |
require("pack").setup({ plugins = { specs } }) |
Specs are placed under the explicit plugins table key. |
opts = {} |
Supported | Supported | Identical behavior (automatically calls require(main).setup(opts)). |
config = fn |
Supported | Supported | Identical callback execution after loading. |
lazy = true |
Supported | Supported | Identical deferral behavior. |
cmd / ft / event / keys |
Supported | Supported | Identical trigger keys for lazy loading. |
dependencies = { ... } |
Supported | Supported | Identical recursive dependency resolution. |
import = "plugins" |
Supported | Supported | Identical modular spec file importing. |
| Lockfile Path | lazy-lock.json |
nvim-pack-lock.json |
Managed natively by Neovim 0.12 vim.pack. |
| Commands | :Lazy / :Lazy sync |
:Pack / :Pack sync |
Identical interactive dashboard and sync workflow. |
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", lazypath
})
end
vim.opt.rtp:prepend(lazypath)
vim.g.mapleader = " "
require("lazy").setup({
{ "folke/which-key.nvim", opts = {} },
{ "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },
{ "neovim/nvim-lspconfig", lazy = true, event = "BufReadPre" },
{ import = "plugins" },
})
-- Bootstrap pack.nvim (Neovim 0.12+ Native)
vim.loader.enable()
vim.g.mapleader = " "
vim.pack.add({ { src = "https://github.com/igmrrf/pack.nvim", branch = "main" } })
vim.cmd.packadd("pack.nvim")
require("pack").setup({
plugins = {
-- Include self-management spec
{ "igmrrf/pack.nvim", branch = "main" },
{ "folke/which-key.nvim", opts = {} },
{ "nvim-treesitter/nvim-treesitter", build = ":TSUpdate" },
{ "neovim/nvim-lspconfig", lazy = true, event = "BufReadPre" },
{ import = "plugins" },
}
})
lua/plugins/*.lua require zero syntax changes! Simply wrap them inside require("pack").setup({ plugins = { ... } }) in your main init.lua.
| packer.nvim Option | pack.nvim Equivalent | Migration Details |
|---|---|---|
use 'owner/repo' |
{ "owner/repo" } |
Standard table spec string. |
requires = { ... } |
dependencies = { ... } |
Dependencies load recursively prior to the main plugin. |
run = ":TSUpdate" |
build = ":TSUpdate" |
Build hook executed after install/update (shell, Ex-cmd, or Lua fn). |
setup = function() ... end |
init = function() ... end |
Callback executed BEFORE the plugin is loaded into runtimepath. |
config = function() ... end |
opts = {} or config = fn |
Use opts = {} shorthand to automatically run setup. |
opt = true |
lazy = true |
Defers loading until triggered by event, ft, cmd, or keys. |
:PackerSync / :PackerCompile |
:Pack sync |
Zero compilation files to generate; native vim.pack manages load paths directly. |
-- init.lua using packer.nvim
require('packer').startup(function(use)
use 'wbthomason/packer.nvim'
use {
'nvim-telescope/telescope.nvim',
requires = { {'nvim-lua/plenary.nvim'} }
}
use {
'nvim-treesitter/nvim-treesitter',
run = ':TSUpdate'
}
use {
'neovim/nvim-lspconfig',
opt = true,
ft = { 'lua', 'python' },
config = function()
require('lspconfig').pyright.setup({})
end
}
end)
-- init.lua using pack.nvim (Neovim 0.12+)
vim.loader.enable()
vim.pack.add({ { src = "https://github.com/igmrrf/pack.nvim", branch = "main" } })
vim.cmd.packadd("pack.nvim")
require("pack").setup({
plugins = {
{ "igmrrf/pack.nvim" },
{
"nvim-telescope/telescope.nvim",
dependencies = { "nvim-lua/plenary.nvim" }
},
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate"
},
{
"neovim/nvim-lspconfig",
lazy = true,
ft = { "lua", "python" },
opts = {
servers = { pyright = {} }
}
}
}
})
packadd
Instead of calling vim.cmd.packadd(...) manually inside subfiles, let pack.nvim control eager or lazy loading automatically.
Inside lua/plugins/*.lua files, return spec tables directly. Use { import = "plugins" } in your main setup to auto-load all specs.
-- Return the spec table directly! No manual vim.pack.add calls inside.
return {
"nvim-telescope/telescope.nvim",
lazy = true,
cmd = "Telescope",
dependencies = { "nvim-lua/plenary.nvim" },
opts = {
defaults = {
file_ignore_patterns = { "node_modules", "%.git/" },
},
},
}
| Spec Key | Type | Description |
|---|---|---|
| [1] / src | string | Plugin repository ("owner/repo"), full Git URL, or local path. |
| as / name | string | Custom directory alias or name for the plugin. |
| dir | string | Local development path for local plugins (bypasses git clone). |
| lazy | boolean | Defers plugin loading until triggered by event, ft, cmd, or keys. |
| priority | number | Load order priority for eager plugins (higher loads first, default 50). |
| enabled | boolean|fn | Toggle to enable or completely skip plugin spec evaluation. |
| cond | boolean|fn | Conditional callback to gate plugin loading at runtime. |
| main | string | Overrides target module name passed to require(main).setup(opts). |
| opts | table | Options table automatically passed to require(main).setup(opts). |
| config | fn | Custom callback executed AFTER plugin is loaded (overrides opts). |
| init | fn | Callback executed BEFORE plugin is loaded into runtimepath. |
| build | string|fn | Shell command, Ex-command, or Lua function post-install/update. |
| cmd | string|table | User command(s) triggering lazy loading. |
| ft | string|table | Filetype(s) triggering lazy loading. |
| event | string|table | Autocmd event(s) or pattern(s) triggering lazy loading (e.g. "BufReadPre"). |
| pattern | string|table | Pattern filter for autocmd event lazy triggers. |
| keys | string|table | Keymap shortcut(s) triggering lazy loading or registering keybindings. |
| module | string | Custom module name for require() trigger tracking. |
| dependencies | table | List of dependent plugin specs loaded prior to this plugin. |
| branch / tag / commit | string | Pin to git branch, tag, or commit hash. |
| version | string | Pin to semver version range (e.g. "^1.0.0"). |
| category / tags | string|table | Category or tag metadata for dashboard filtering (cat:lsp, tag:ui). |
| Ex-Command | Description |
|---|---|
:Pack | Opens the interactive floating dashboard UI. |
:Pack sync | Syncs all managed plugins (installs missing & pulls updates). |
:Pack update [name] | Updates a specific plugin or all plugins. |
:Pack clean | Removes unmanaged/deleted plugin directories from disk. |
:Pack restore | Rolls every plugin back to revisions pinned in nvim-pack-lock.json. |
:Pack repair | Realigns lockfile revisions to installed HEAD commits. |
:Pack build [name] | Re-runs the build hook for a specific plugin or all plugins. |
:Pack load <name> | Immediately loads a lazy plugin. |
:Pack delete <name> | Removes a plugin from state and deletes it via native vim.pack. |
:Pack profile | Displays startup profile ASCII timeline breaking down plugin load times. |
:Pack diff | Displays a structured diff of pending commits for outdated plugins. |
| Keymap | Action Description |
|---|---|
| q | Close dashboard window or popups. |
| ? | Show interactive keymap help popup window. |
| <CR> | Toggle inline plugin details expansion. |
| K | Show full detail popup for cursor plugin (branch, HEAD commit, revision). |
| <Tab> / <S-Tab> | Cycle dashboard tabs forward or backward. |
| 1 / 2 / 3 | Jump directly to tab 1 (All), tab 2 (Outdated), or tab 3 (Disabled). |
| <Space> | Toggle multi-select checkbox for cursor plugin. |
| v | Toggle multi-selection UI mode / clear active selections. |
| S | Sync all managed plugins. |
| s | Sync plugin under cursor. |
| C | Clean unmanaged plugin directories. |
| d | Delete plugin under cursor from disk. |
| D | Delete all disabled plugins from disk. |
| x | Toggle disable/enable state for plugin under cursor. |
| c | Check for outdated plugins via concurrency-limited git fetch. |
| u | Update cursor or selected plugin. |
| U | Update all outdated plugins. |
| l | View streaming git logs for plugin under cursor. |
| p | Display startup profiling timeline. |
| f | Filter plugins by name, cat:category, or tag:tag. |
| Feature | pack.nvim | lazy.nvim | pckr.nvim | paq-nvim | vim-plug |
|---|---|---|---|---|---|
| Minimum Neovim | 0.12+ | 0.8+ | 0.7+ | 0.5+ | Vim 7.4 / Nvim 0.2+ |
| Backend Engine | Native vim.pack | Custom Lua | Native packpath | Native packpath | Custom Vimscript |
| Lockfile | nvim-pack-lock.json | lazy-lock.json | Custom | None | None (snapshots) |
| Codebase Size | ~2,000 lines | ~20,000+ lines | ~3,500 lines | ~600 lines | ~2,700 lines |
| Lazy Triggers | cmd, event, ft, keys, cond | cmd, event, ft, keys, cond | cmd, event, ft, keys | None | on (cmd), for (ft) |
| Dependencies | Yes (dependencies) | Yes (dependencies) | Yes (requires) | No | No |
| Modular Specs | Yes ({ import = "..." }) | Yes ({ import = "..." }) | No | No | No |
| Disk Adoption | Yes (Native) | No | Partial | Partial | No |