Overview & Quickstart
pack.nvim combines Neovim 0.12's native C-powered vim.pack package management with a modern floating dashboard, precompiled ftdetect bytecode cache, and non-blocking background git probes.
-- 1. Enable Neovim's built-in bytecode loader (Line 1)
vim.loader.enable()
-- 2. Bootstrap pack.nvim with 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({
plugins = {
{ "igmrrf/pack.nvim" }, -- pack manages itself
{ import = "plugins" }, -- imports lua/plugins/*.lua
},
})
✅ Requirements
- Neovim 0.12+ (delegates all cloning and lockfile ownership to
vim.pack) - git on your system
PATH
⚡ Key Highlights
- Native C Engine: All plugins reside under
<stdpath("data")>/site/pack/core/opt. - Bytecode Cache: Pre-compiles filetype detectors into
pack_ftdetect_cache.lua. - Zero Dependencies: Under 2,000 lines of pure, rock-solid Lua code.
Basic Fallback Bootstrap
Standard vim.uv.fs_stat fallback snippet with automatic git cloning if pack.nvim is missing from disk.
-- Ensure pack.nvim is installed before running setup
local packpath = vim.fn.stdpath("data") .. "/site/pack/core/opt/pack.nvim"
if not (vim.uv or vim.loop).fs_stat(packpath) then
local out = vim.fn.system({
"git", "clone", "--filter=blob:none", "--branch=main",
"https://github.com/igmrrf/pack.nvim.git", packpath,
})
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({ { "Failed to clone pack.nvim:\n" .. out, "ErrorMsg" } }, true, {})
return
end
end
vim.opt.rtp:prepend(packpath)
require("pack").setup({
plugins = { { "igmrrf/pack.nvim" } }
})
Native vim.pack.add Bootstrap
Minimal bootstrapping pattern utilizing Neovim 0.12's native vim.pack.add API.
-- 1. Enable bytecode cache
vim.loader.enable()
-- 2. Bootstrap pack.nvim natively
vim.pack.add({ { src = "https://github.com/igmrrf/pack.nvim", branch = "main" } })
vim.cmd.packadd("pack.nvim")
-- 3. Initialize pack.nvim
require("pack").setup({
plugins = { { "igmrrf/pack.nvim" } }
})
Live :Pack Dashboard Simulator
Try the floating terminal interface directly in your browser. Switch tabs, inspect specs with K, or run simulated updates.
Config Generator
Customize setup parameters and select preset plugins to generate valid Neovim 0.12 Lua configuration.
-- Lua code generated dynamically...
Core Capabilities & Engine
Deep dive into pack.nvim's native C integration and performance design.
Native C Core Engine
Delegates cloning, branch checkout, revision pinning, and lockfile creation to Neovim 0.12's C-native vim.pack backend under <stdpath("data")>/site/pack/core/opt.
vim.pack.add() enginePrecompiled ftdetect Cache
Pre-compiles lazy plugins' filetype detectors into a single cache block (pack_ftdetect_cache.lua) sourced at startup so filetypes match instantly before plugin scripts load.
Async Read-Only Git Probes
Non-blocking background concurrency-limited git probes via vim.system provide live outdated commit counts and commit diff previews without UI stutter.
Native Lockfile Ownership
Native nvim-pack-lock.json holds canonical commit revisions. `:Pack restore` rolls every plugin back to lockfile state, while `:Pack repair` re-aligns hashes.
Lazy Loading Triggers Guide
Learn how to defer plugin load times until triggered by commands, filetypes, keymaps, autocmd events, or patterns.
require("pack").setup({
plugins = {
-- 1. Load on Command
{
"nvim-telescope/telescope.nvim",
lazy = true,
cmd = "Telescope", -- Loads when :Telescope is executed
},
-- 2. Load on FileType
{
"nvim-treesitter/nvim-treesitter",
lazy = true,
ft = { "lua", "python", "rust" }, -- Loads when opening these filetypes
},
-- 3. Load on Event with Pattern
{
"rust-lang/rust.vim",
lazy = true,
event = "BufReadPre *.rs", -- Pattern matching for autocmd events
},
-- 4. Load on Keymap
{
"folke/flash.nvim",
lazy = true,
keys = { "s", { "S", mode = { "n", "x" } } },
}
}
})
Advanced Hooks & Dependencies
Configure context-aware init and config callbacks, post-install build hooks, nested dependencies, and local dev plugins.
require("pack").setup({
plugins = {
-- Dependencies: plenary loads automatically before telescope
{
"nvim-telescope/telescope.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
cmd = "Telescope",
},
-- Build Hooks: run 'make' after cloning/updating
{
"nvim-telescope/telescope-fzf-native.nvim",
build = "make", -- can also be a Lua function(plugin)
},
-- Context-Aware Config & Init Hooks
{
"nvim-lualine/lualine.nvim",
init = function(plugin)
-- Runs BEFORE plugin loads (plugin.path available)
vim.g.lualine_path = plugin.path
end,
config = function(plugin, opts)
-- Runs AFTER plugin loads
require("lualine").setup(opts)
end
},
-- Conditional Loading & Priority
{
"folke/tokyonight.nvim",
priority = 1000, -- Load early
cond = function() return not vim.g.vscode end,
},
-- Local plugin path
{
"~/projects/my-local-plugin",
config = function() require("my-local-plugin").setup() end
}
}
})
Modular Configuration
Split your plugin specifications cleanly across multiple files in lua/plugins/*.lua using { import = "plugins" }.
-- 1. Main entry point (~/.config/nvim/init.lua)
vim.pack.add({ "https://github.com/igmrrf/pack.nvim" })
vim.cmd.packadd("pack.nvim")
require("pack").setup({
plugins = {
{ "igmrrf/pack.nvim" },
{ import = "plugins" }, -- Imports all files in lua/plugins/**/*.lua
}
})
-- 2. Modular spec file (~/.config/nvim/lua/plugins/tabscope.lua)
-- IMPORTANT: Simply return the spec table (do NOT call vim.pack.add inside)
return {
"backdround/tabscope.nvim",
lazy = true,
opts = {},
}
Adopting Disk vim.pack Plugins
pack.nvim automatically recognizes plugins installed via native vim.pack.add under site/pack/core/opt without re-cloning.
require("pack").setup({
plugins = {
-- Already cloned on disk by native vim.pack? Just declare it in spec:
{ "nvim-lua/plenary.nvim" },
}
})
Customizing Dashboard UI & Icons
Configure floating window borders and customize dashboard status icons.
require("pack").setup({
ui = {
-- Border styles: "single", "double", "rounded", "solid", "shadow"
border = "double",
icons = {
loaded = "🟢",
not_loaded = "⚪",
error = "🔴",
sync = "🔄"
}
},
plugins = { { "igmrrf/pack.nvim" } }
})
Migrating from lazy.nvim
pack.nvim is intentionally designed to feel familiar to lazy.nvim users. Here is how to convert your specs.
-- BEFORE: lazy.nvim
-- local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
-- require("lazy").setup({ { "folke/which-key.nvim", opts = {} } })
-- AFTER: pack.nvim
vim.g.mapleader = " "
vim.pack.add({ { src = "https://github.com/igmrrf/pack.nvim", branch = "main" } })
vim.cmd.packadd("pack.nvim")
require("pack").setup({
plugins = {
{ "igmrrf/pack.nvim", branch = "main" },
{ "folke/which-key.nvim", opts = {} },
{ import = "plugins" },
}
})
Migrating from packer.nvim
packer.nvim used imperative use calls. In pack.nvim, features map to declarative spec tables.
-- Mapping Table:
-- packer `requires` -> pack `dependencies`
-- packer `run` -> pack `build`
-- packer `setup` -> pack `init`
require("pack").setup({
plugins = {
{ "igmrrf/pack.nvim" },
{
"nvim-telescope/telescope.nvim",
dependencies = { "nvim-lua/plenary.nvim" }
},
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate"
}
}
})
Migrating from Imperative vim.pack.add
Stop wrapping specs in imperative vim.pack.add(...) calls inside modular files; simply return table specs.
-- lua/plugins/myplugin.lua
-- Do NOT call vim.pack.add or vim.cmd.packadd inside modular files!
-- Simply return the spec table:
return {
"nvim-lua/plenary.nvim",
{
"nvim-telescope/telescope.nvim",
config = function()
require("telescope").setup({})
end
}
}
Plugin Specification Keys
Exhaustive reference table of supported spec keys.
| Key | Type | Description & Usage |
|---|
All Features Plugin Specification
An exhaustive example showing every single key and option available in pack.nvim.
require("pack").setup({
plugins = {
{
-- 1. Plugin Source
"user/mega-plugin", -- Bare "owner/repo" -> GitHub
as = "mega-plugin-custom-name", -- Custom directory alias
-- 2. Loading Mechanics
lazy = true, -- Defer loading until triggered
priority = 100, -- Eager load order priority
enabled = true, -- Hard toggle
cond = function(plugin) return not vim.g.vscode end,
main = "mega.core", -- Override main module name
-- 3. Triggers
cmd = { "MegaCommand", "MegaToggle" },
ft = { "lua", "python" },
event = "BufReadPre *.md",
keys = { { "<leader>m", "<cmd>MegaToggle<cr>", desc = "Toggle" } },
-- 4. Dependencies
dependencies = { "nvim-lua/plenary.nvim" },
-- 5. Hooks & Config
init = function(plugin) vim.g.mega_dir = plugin.path end,
opts = { theme = "dark" },
config = function(plugin, opts) require("mega.core").setup(opts) end,
build = "make install",
-- 6. Pinning
branch = "main",
}
}
})
Commands & Dashboard Keymaps
Vim user commands and interactive dashboard keyboard shortcuts.
Comparison Matrix
Comparing pack.nvim against legacy & modern Neovim plugin managers.
| Feature / Dimension | 📦 pack.nvim | lazy.nvim | pckr.nvim | paq-nvim | vim-plug |
|---|---|---|---|---|---|
| Minimum Neovim | 0.12+ (vim.pack) | 0.8+ | 0.7+ | 0.5+ | Vim 7.4 / Nvim 0.2+ |
| Backend Engine | Native C (vim.pack) |
Custom Lua engine | Native packpath |
Native packpath |
Vimscript engine |
| Storage Location | stdpath("data")/site/pack/core/opt |
stdpath("data")/lazy |
stdpath("data")/site/pack/pckr/opt |
stdpath("data")/site/pack/paq/opt |
~/.config/nvim/plugged |
| Lockfile Engine | Native nvim-pack-lock.json |
Custom lazy-lock.json |
Custom lockfile | None | None (snapshots) |
| Codebase Size | ~2,000 lines of Lua | ~20,000+ lines of Lua | ~3,500 lines of Lua | ~600 lines of Lua | ~2,700 lines of Vimscript |
| Maintenance Model | Feature-frozen & Stable | Active development | Maintenance | Minimal / Stable | Maintenance |