Tips & Migration
August 2, 2026 · View on GitHub
Upgrading from 1.x to 2.0
2.0.0 removes every option and command that was deprecated during the 1.x series. The renamed options (confirm, disable_vim_loader, plugins_dir) emitted runtime warnings throughout 1.x; the legacy :Z* commands are removed in favor of the :ZPack <subcommand> dispatcher. Removed options are now ignored silently, so update your setup() call before upgrading:
| Removed in 2.0 | Replacement |
|---|---|
setup({ confirm = … }) | setup({ defaults = { confirm = … } }) |
setup({ disable_vim_loader = true }) | setup({ performance = { vim_loader = false } }) |
setup({ plugins_dir = 'dir' }) | a { import = 'dir' } entry in your spec list |
setup({ cmd_prefix = 'Z' }) | setup({ cmd_name = 'Z' }) |
setup({ auto_import = … }) | removed — was already a no-op in 1.x |
require('zpack').add(…) | removed — was already a no-op in 1.x |
Commands
The per-action :Z* commands are now subcommands of a single :ZPack command:
| 1.x | 2.0 |
|---|---|
:ZUpdate | :ZPack update |
:ZRestore | :ZPack restore |
:ZClean | :ZPack clean |
:ZBuild | :ZPack build |
:ZLoad | :ZPack load |
:ZDelete | :ZPack delete |
The command name is configurable via cmd_name (default ZPack). If you relied on the old cmd_prefix = 'Z', set cmd_name = 'Z' to type :Z update, :Z clean, etc. The ! bang is still supported where it was before (e.g. :ZPack! build, :ZPack! delete).
Migrating from lazy.nvim
Most of your lazy.nvim plugin specs will work as-is with zpack. However, zpack follows vim.pack conventions over lazy.nvim conventions, and is missing a few advanced features:
- version pinning: lazy.nvim's
versionfield maps to zpack'ssem_version. See Spec Reference and version pinning examples - dev mode: Set
dev = trueon a spec and configuresetup({ dev = { path = '~/projects' } })— the source is rewritten to<path>/<plugin-name>. Live file-watch / auto-reload is out of scope; use:ZPack reload {plugin}to manually re-source. See Spec Reference fordev/deactivate - profiling: Use
nvim --startuptime startuptime.log. Also refer to example Neovim Profiler script - default lazy plugins: lazy.nvim's community specs silently default top-level specs for utility libraries like
plenary.nvimtolazy = true, even without lazy triggers or a lazy parent. zpack respects your specs as-written, so setlazy = trueexplicitly on such specs if you want the same default — or setdefaults.lazy = trueinsetup()to apply it to every spec enabledvscond: lazy.nvim collapses both to a single "disabled" state. zpack splits them:enabled = falseskips install entirely (the plugin is never cloned), whilecond = falseinstalls the plugin but skips its load (so it still shows up undervim.pack's data dir). Useenabledwhen you want lazy.nvim's "don't install at all" behavior
Gotchas
Known gotchas when using zpack:
- install/update feedback:
vim.packsurfaces install/update progress via:messages(e.g.vim.pack: Downloading updates (0/83)). These messages are hidden if you havevim.opt.cmdheight = 0— raise it, check:messages, or route them through a notifier like snacks.notifier, nvim-notify, or noice.nvim. Also see noice.nvim with vim.pack for compatibility notes
Compatibility Notes
Snacks.nvim dashboard with zpack.nvim
The default Snacks.nvim dashboard configuration includes a startup time section that has a hard dependency on lazy.nvim. This will cause errors with any other plugin manager, not just zpack.
To work around this, remove the startup section from your dashboard configuration:
require('snacks').setup({
dashboard = {
sections = {
{ section = "header" },
{ section = "keys", gap = 1, padding = 1 },
-- { section = "startup" }, -- Remove this line (depends on lazy.nvim)
},
}
})
See snacks.nvim#1778 for more details.
noice.nvim with vim.pack
noice.nvim filters out vim.pack messages by default, which means you won't see install/update notifications from your plugin manager.
To fix this, add a route that explicitly shows vim.pack messages:
require('noice').setup({
routes = {
{
filter = {
event = "msg_show",
find = "vim.pack",
},
},
},
})
Lockfile location
vim.pack writes its lockfile to $XDG_CONFIG_HOME/nvim/nvim-pack-lock.json — i.e. next to your init.lua. On Neovim 0.13+ the path is configurable through the 'packlockfile' option (see :h 'packlockfile'), so you can keep it out of your config repo:
vim.o.packlockfile = vim.fs.joinpath(vim.fn.stdpath('data'), 'nvim-pack-lock.json')
Two caveats:
'packlockfile'must be set before the firstvim.packcall, which in practice means before thevim.pack.add()that installs zpack itself — put it at the very top of yourinit.lua.- Changing the path does not move the existing file. Move (or delete and regenerate with
:ZPack update) the old lockfile, otherwisevim.packstarts from an empty one.
zpack needs no configuration for this — it delegates to vim.pack for lockfile reads and writes, so :ZPack update and :ZPack restore follow whatever 'packlockfile' points at.
Native vim.pack commands
On Neovim 0.13+, the :ZPack update, restore, and delete subcommands have native vim.pack command equivalents (:packupdate, :packupdate ++lockfile, :packdel). Use whichever you prefer — zpack keeps its session state in sync either way. See :h zpack-native-commands for the full mapping.