Neovim 0.12+
Native vim.pack Wrapper with Floating Dashboard

pack.nvim

A blazingly fast, modern Neovim plugin manager engineered on top of Neovim 0.12's native package management API (vim.pack). Zero-overhead runtime, lockfile reproducibility, and an interactive floating UI.

Highlights & Architecture

Native Neovim 0.12 Core

Delegates all git clones, checkouts, and package handling directly to vim.pack. Zero custom wrapper weight on startup.

Native Lockfile Sync

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.

Disk Plugin Adoption

Discovers pre-existing disk plugins and adopts them seamlessly on startup tagged as (native).

Neovim 0.12 Requirement: 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.

Installation & Bootstrapping

Bootstrapping pack.nvim is ultra-clean using Neovim 0.12's native vim.pack.add. Place this code snippet at the top of your init.lua.

init.lua (Native Bootstrap)
-- 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")

Interactive Dashboard Simulator

Experience pack.nvim's floating UI directly in your browser. Test multi-select toggling (<Space>), tab navigation, status indicators, and streaming logs.

Neovim Floating Dashboard — :Pack
🔒 pack.lock active
S Sync All <Space> Multi-Select K Details l Logs ? Help
Press q to exit UI

Interactive Config Generator

Build production-ready pack.nvim specs with modern options like opts, main, lazy triggers, build hooks, and lockfile generation.

Configure Spec Options

Generated Spec Output

Output Lua Spec
-- Generated spec will appear here

Core Capabilities & Engine

Explore the technical architecture powering pack.nvim.

Auto Directory Migration

Toggling lazy = true/false automatically migrates the plugin directory between opt/ and start/ seamlessly without manual intervention.

Multi-Select & Bulk Actions

Select multiple plugins with <Space> in the dashboard to perform batch update, disable, clean, or delete operations cleanly.

Startup Profiling

Run :Pack profile to render visual ASCII startup timelines breaking down exact load durations for every plugin.

Lazy Loading Triggers & opts

Optimize your startup time to under 10ms with flexible lazy loading triggers and shorthand setup keys.

Lazy Triggers Example
{
  "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 = {} },
  },
}

Lockfile & Sync Engine

pack.nvim leverages Neovim 0.12's native package lockfile (nvim-pack-lock.json) alongside nvim-pack-extra.json to deliver 100% reproducible environments.

Native Lockfile (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.

Extra State Store (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.

Lockfile Repair

Execute :Pack repair to realign lockfile revisions to installed plugin HEAD commits whenever manual disk edits or custom checkouts occur.

Adopting Existing Disk Plugins

Because pack.nvim shares Neovim 0.12's native site/pack/core/opt/ path, any plugin already present on disk can be adopted automatically without re-cloning.

Automatic Native Scanning: On startup, pack.nvim queries vim.pack for unmanaged disk plugins. Unmanaged plugins appear in the :Pack dashboard marked with a (native) tag and function normally!
Declaring Adopted Plugins in Specs
-- 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 = {} },
  }
})

Advanced Hooks & Dependencies

Execute post-install build steps and manage recursive dependency chains cleanly.

Build Hook Types

Hook TypeExample SpecBehavior
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.
Complex Spec Example
{
  "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,
}

Migrating from lazy.nvim

pack.nvim is designed to feel 1:1 familiar to lazy.nvim users. Your existing plugin specs, lazy triggers (cmd, event, ft, keys), opts = {} shorthands, and { import = "..." } structures work seamlessly, while running on Neovim 0.12's zero-overhead vim.pack native core.

Key Differences & Compatibility Matrix

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.

Code Conversion Example

BEFORE: lazy.nvim

-- 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" },
})

AFTER: pack.nvim

-- 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" },
  }
})
Migration Takeaway: Almost all your individual plugin spec files in lua/plugins/*.lua require zero syntax changes! Simply wrap them inside require("pack").setup({ plugins = { ... } }) in your main init.lua.

Migrating from packer.nvim

packer.nvim used imperative use declarations and required running :PackerSync and :PackerCompile. pack.nvim is fully declarative, requires zero compilation steps, and leverages Neovim 0.12+ native package management.

Key Terminology Mapping

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.

Code Conversion Example

BEFORE: packer.nvim

-- 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)

AFTER: pack.nvim

-- 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 = {} }
      }
    }
  }
})

Migrating from Imperative vim.pack

Neovim 0.12 introduced raw vim.pack.add(). If you are using raw imperative calls scattered across your lua files, convert them to pack.nvim's clean declarative spec model for full dashboard tracking and lazy loading.

Key Refactoring Concepts

Stop Manual packadd

Instead of calling vim.cmd.packadd(...) manually inside subfiles, let pack.nvim control eager or lazy loading automatically.

Return Tables from Files

Inside lua/plugins/*.lua files, return spec tables directly. Use { import = "plugins" } in your main setup to auto-load all specs.

Modular Import Example

lua/plugins/telescope.lua
-- 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/" },
    },
  },
}

Plugin Spec Keys Reference

Exhaustive dictionary of all supported pack.nvim specification keys.

Spec Key Type Description
[1] / srcstringPlugin repository ("owner/repo"), full Git URL, or local path.
as / namestringCustom directory alias or name for the plugin.
dirstringLocal development path for local plugins (bypasses git clone).
lazybooleanDefers plugin loading until triggered by event, ft, cmd, or keys.
prioritynumberLoad order priority for eager plugins (higher loads first, default 50).
enabledboolean|fnToggle to enable or completely skip plugin spec evaluation.
condboolean|fnConditional callback to gate plugin loading at runtime.
mainstringOverrides target module name passed to require(main).setup(opts).
optstableOptions table automatically passed to require(main).setup(opts).
configfnCustom callback executed AFTER plugin is loaded (overrides opts).
initfnCallback executed BEFORE plugin is loaded into runtimepath.
buildstring|fnShell command, Ex-command, or Lua function post-install/update.
cmdstring|tableUser command(s) triggering lazy loading.
ftstring|tableFiletype(s) triggering lazy loading.
eventstring|tableAutocmd event(s) or pattern(s) triggering lazy loading (e.g. "BufReadPre").
patternstring|tablePattern filter for autocmd event lazy triggers.
keysstring|tableKeymap shortcut(s) triggering lazy loading or registering keybindings.
modulestringCustom module name for require() trigger tracking.
dependenciestableList of dependent plugin specs loaded prior to this plugin.
branch / tag / commitstringPin to git branch, tag, or commit hash.
versionstringPin to semver version range (e.g. "^1.0.0").
category / tagsstring|tableCategory or tag metadata for dashboard filtering (cat:lsp, tag:ui).

Commands & Dashboard Keymaps

Complete reference of Neovim Ex-commands and interactive dashboard keymaps.

Neovim Ex-Commands

Ex-CommandDescription
:PackOpens the interactive floating dashboard UI.
:Pack syncSyncs all managed plugins (installs missing & pulls updates).
:Pack update [name]Updates a specific plugin or all plugins.
:Pack cleanRemoves unmanaged/deleted plugin directories from disk.
:Pack restoreRolls every plugin back to revisions pinned in nvim-pack-lock.json.
:Pack repairRealigns 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 profileDisplays startup profile ASCII timeline breaking down plugin load times.
:Pack diffDisplays a structured diff of pending commits for outdated plugins.

Dashboard Floating Keymaps

KeymapAction Description
qClose dashboard window or popups.
?Show interactive keymap help popup window.
<CR>Toggle inline plugin details expansion.
KShow full detail popup for cursor plugin (branch, HEAD commit, revision).
<Tab> / <S-Tab>Cycle dashboard tabs forward or backward.
1 / 2 / 3Jump directly to tab 1 (All), tab 2 (Outdated), or tab 3 (Disabled).
<Space>Toggle multi-select checkbox for cursor plugin.
vToggle multi-selection UI mode / clear active selections.
SSync all managed plugins.
sSync plugin under cursor.
CClean unmanaged plugin directories.
dDelete plugin under cursor from disk.
DDelete all disabled plugins from disk.
xToggle disable/enable state for plugin under cursor.
cCheck for outdated plugins via concurrency-limited git fetch.
uUpdate cursor or selected plugin.
UUpdate all outdated plugins.
lView streaming git logs for plugin under cursor.
pDisplay startup profiling timeline.
fFilter plugins by name, cat:category, or tag:tag.

Comparison Matrix

Detailed side-by-side comparison of pack.nvim against popular Neovim plugin managers.

Feature pack.nvim lazy.nvim pckr.nvim paq-nvim vim-plug
Minimum Neovim0.12+0.8+0.7+0.5+Vim 7.4 / Nvim 0.2+
Backend EngineNative vim.packCustom LuaNative packpathNative packpathCustom Vimscript
Lockfilenvim-pack-lock.jsonlazy-lock.jsonCustomNoneNone (snapshots)
Codebase Size~2,000 lines~20,000+ lines~3,500 lines~600 lines~2,700 lines
Lazy Triggerscmd, event, ft, keys, condcmd, event, ft, keys, condcmd, event, ft, keysNoneon (cmd), for (ft)
DependenciesYes (dependencies)Yes (dependencies)Yes (requires)NoNo
Modular SpecsYes ({ import = "..." })Yes ({ import = "..." })NoNoNo
Disk AdoptionYes (Native)NoPartialPartialNo