Skip to main content

Streamline Your Neovim Workflow: Switching from packer.nvim to lazy.nvim

Neovim · Plugins · Migration

If your current Neovim setup still uses packer.nvim, this guide will help you move to lazy.nvim without guessing. It explains what changes, what stays the same, how to rewrite your plugin specs, and how to structure the new setup in a beginner-friendly way.

Why migrate now

packer.nvim is unmaintained, and its own README recommends lazy.nvim.

What gets easier

No manual compile step, cleaner plugin specs, automatic module lazy-loading, and a lockfile workflow.

Best beginner mindset

You are not rebuilding Neovim from scratch. You are replacing the plugin manager and translating the plugin definitions.

Start here

What this article assumes

This guide assumes you already have a working Neovim configuration and that your plugins are currently declared with packer.nvim. You do not need expert Lua knowledge. You only need to be comfortable editing your Neovim config files and reading simple plugin declarations.

References: packer.nvim repo · packer.nvim README · lazy.nvim repo · lazy.nvim migration guide

The important context is simple. packer.nvim was a very influential plugin manager, but its README now says the repository is unmaintained and recommends lazy.nvim. That means a migration is no longer just about taste. For many users, it is the most practical maintenance move.

One important clarification LazyVim is not a replacement for lazy.nvim. LazyVim is a full Neovim configuration built on top of lazy.nvim. In this post, we use LazyVim mainly as a model for file structure, not as the thing you are migrating to.
Mental model

What actually changes when you leave packer.nvim

References: packer.nvim README · lazy.nvim README · lazy.nvim plugin spec · lazy.nvim usage

With packer.nvim, your plugins usually live inside a require("packer").startup(function(use) ... end) block. Packer also expects a compile step to generate its lazy-loader file. In many configs, that leads to a PackerCompile autocommand and a generated packer_compiled.lua or packer_compiled.vim file.

With lazy.nvim, the plugin manager works with plain plugin spec tables. You do not wrap everything in use(...), and you do not manually compile a loader file. Instead, lazy.nvim manages loading, caching, and startup behavior itself.

  • Packer: startup block, use declarations, manual compile workflow.
  • Lazy: plain spec tables, automatic loading rules, no manual compile command.
  • Packer: commands like :PackerSync and :PackerCompile.
  • Lazy: commands like :Lazy sync, :Lazy update, :Lazy clean, :Lazy profile, and a lazy-lock.json file for reproducible plugin revisions.
The simplest way to think about it Your plugins do not disappear. Your keymaps do not disappear. Your editor preferences do not disappear. The main job is translating how the plugins are declared and loaded.
Recommended structure

Use a file layout that matches lazy.nvim and LazyVim

References: lazy.nvim installation · lazy.nvim structuring docs · LazyVim configuration · LazyVim plugins

For a beginner migration, the cleanest structure is the one lazy.nvim already documents and LazyVim also uses: put the bootstrap and setup logic in lua/config/lazy.lua, then put plugin specs in one or more files under lua/plugins/.

A minimal target layout looks like this:

~/.config/nvim ├── init.lua └── lua ├── config │ └── lazy.lua └── plugins ├── core.lua ├── lsp.lua └── ui.lua

Your init.lua can stay very small:

require("config.lazy")

Then create a simple lua/config/lazy.lua bootstrap file:

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", "--branch=stable", "https://github.com/folke/lazy.nvim.git", lazypath, }) end vim.opt.rtp:prepend(lazypath) vim.g.mapleader = " " vim.g.maplocalleader = "\\" require("lazy").setup({ { import = "plugins" }, }, { checker = { enabled = true }, })

That structure gives you a clean migration target. You can start with one lua/plugins/init.lua file if you want, then split plugins into smaller files later.

Migration plan

The safest step-by-step migration path

  1. Back up your current config. Copy your entire Neovim config directory somewhere safe before you touch anything.
  2. List the plugins you already use. Open your current packer.nvim file and identify the actual plugin declarations you want to keep.
  3. Remove packer-specific bootstrapping. Delete the ensure_packer() block and remove use "wbthomason/packer.nvim" from your managed plugins.
  4. Remove packer-specific compile behavior. Delete any BufWritePost ... | PackerCompile autocommands and stop loading any generated packer_compiled file.
  5. Add the lazy.nvim bootstrap file. Create init.lua and lua/config/lazy.lua as shown above.
  6. Move plugin specs into lua/plugins/. Start with one file if that feels easier.
  7. Translate each packer spec into a lazy spec. This is the main work. The field names are similar, but not identical.
  8. Start Neovim and run :Lazy sync. This installs missing plugins, updates the set, and cleans what is no longer needed.
  9. Run :Lazy health and :Lazy profile. Confirm that lazy.nvim itself is healthy and inspect startup behavior once the migration works.
Do not skip this cleanup step A very common mistake is leaving the old packer compile autocommand in place. If you migrate to lazy.nvim but still run PackerCompile on save, you will keep one foot in the old system and make debugging harder.
Cheat sheet

How packer fields map to lazy fields

Reference: lazy.nvim migration guide

  • setup becomes init
  • requires becomes dependencies
  • as becomes name
  • opt = true becomes lazy = true
  • run becomes build
  • lock becomes pin
  • disable = true becomes enabled = false
  • tag = "*" becomes version = "*"
  • after is usually unnecessary; use dependencies only when a plugin must be installed and loaded with another plugin
  • config can still exist, but string-based config does not; use a function instead
  • module usually does not need to be declared because lazy.nvim auto-loads Lua modules on demand
One subtle but important difference In lazy.nvim, dependencies should not become your new default hammer. Many Lua plugins can simply be installed normally and loaded when they are require()d. Only group dependencies when they truly belong to the same plugin load path.
Real conversions

How to rewrite common plugin specs

1) Simple plugin

Packer

use "nvim-lualine/lualine.nvim"

Lazy

return { { "nvim-lualine/lualine.nvim", opts = {} }, }

In lazy.nvim, opts = {} is usually the cleanest way to say, “call this plugin’s setup() with these options.” The docs recommend using opts instead of config whenever possible.

2) Dependency-based plugin

Packer

use { "lewis6991/gitsigns.nvim", requires = { "nvim-lua/plenary.nvim" }, config = function() require("gitsigns").setup() end, }

Lazy

return { { "lewis6991/gitsigns.nvim", dependencies = { "nvim-lua/plenary.nvim" }, opts = {}, }, }

3) Command or event lazy-loading

Packer

use { "iamcco/markdown-preview.nvim", run = "cd app && yarn install", cmd = "MarkdownPreview", }

Lazy

return { { "iamcco/markdown-preview.nvim", build = "cd app && yarn install", cmd = "MarkdownPreview", }, }

4) Filetype loading

Packer

use { "andymass/vim-matchup", event = "VimEnter", }

Lazy

return { { "andymass/vim-matchup", event = "VimEnter", }, }

5) Colorscheme plugin

Colorschemes are the main place where you often do not want lazy loading in the same way you do for ordinary plugins. lazy.nvim’s docs recommend giving your main colorscheme high priority when it loads as a start plugin.

return { { "folke/tokyonight.nvim", lazy = false, priority = 1000, }, }
Commands

What to use instead of packer commands

References: packer.nvim README · lazy.nvim usage · lazy.nvim lockfile

  • :PackerSync becomes :Lazy sync
  • :PackerUpdate becomes :Lazy update
  • :PackerClean becomes :Lazy clean
  • :PackerStatus becomes :Lazy
  • :PackerCompile becomes nothing, because lazy.nvim does not require a manual compile step
  • Packer snapshots become the lazy-lock.json workflow plus :Lazy restore
  • PackerProfile becomes :Lazy profile

Once the migration is complete, commit your lazy-lock.json file. That gives you reproducible plugin revisions across machines and a clean restore target later.

Common mistakes

What usually breaks a beginner migration

  1. Leaving old packer code in place. If your config still sources or compiles packer files, you are not fully migrated.
  2. Trying to keep the use(...) mental model. In lazy.nvim, think in spec tables, not wrapped declarations.
  3. Translating setup to config instead of init. That changes when the code runs.
  4. Using config everywhere. Prefer opts when a plugin exposes setup().
  5. Overusing dependencies. lazy.nvim can auto-load Lua modules when they are required, so not every helper library needs to be nested.
  6. Turning on global lazy-loading too early. lazy.nvim supports defaults.lazy = true, but the docs explicitly warn that you should only do that if you understand the consequences.
  7. Forgetting health and profiling checks. A migration is not done just because Neovim opens. Run :Lazy health and :Lazy profile.
Why LazyVim matters here

What LazyVim teaches even if you do not use LazyVim

References: LazyVim repo · LazyVim README · LazyVim configuration docs · LazyVim plugin docs

LazyVim is useful to study because it proves that a large Neovim setup can stay clean when it is organized around lazy.nvim conventions. Its docs show a small init.lua, a dedicated lua/config/lazy.lua file, and plugin specs living under lua/plugins/. That is a strong model for a beginner migration because it gives you a place for each kind of code.

It also reinforces an important point: plugin configuration in LazyVim is still just lazy.nvim-style plugin specs. So even if you never install LazyVim itself, its file layout is a very good template for cleaning up a formerly packer-based config.

FAQ

Frequently Asked Questions

These are the questions most beginners have when they first replace packer.nvim with lazy.nvim.

Do I need to rewrite my whole Neovim config?

No. You mainly need to replace the plugin manager bootstrap, move to lazy.nvim’s spec format, and remove packer-specific compile behavior.

Do I need to switch to LazyVim too?

No. LazyVim is a full Neovim setup built on lazy.nvim. You can migrate to lazy.nvim without adopting LazyVim.

What happens to packer_compiled.lua?

You no longer need it. Remove the old generated file and any autocommands that regenerate it.

What should I do with use "wbthomason/packer.nvim"?

Delete it. That line was packer managing itself. It does not belong in a lazy.nvim plugin list.

Should every plugin become lazy-loaded?

No. Some plugins should load immediately, especially your colorscheme and anything needed very early in startup. The goal is better loading choices, not maximum laziness.

What replaces packer snapshots?

The normal lazy.nvim workflow is to commit lazy-lock.json and use :Lazy restore when you want the exact locked plugin versions again.

What is the cleanest first migration target?

A tiny init.lua, a single lua/config/lazy.lua bootstrap file, and one plugin file under lua/plugins/. Once that works, split your plugins into multiple files.

How do I know the migration is really done?

Neovim should start with lazy.nvim, :Lazy sync should manage your plugins, there should be no packer compile autocommand left, and your plugin revisions should be tracked in lazy-lock.json.

Conclusion

A good migration from packer.nvim to lazy.nvim is less dramatic than it first appears. The real work is not “learning a whole new Neovim.” The real work is changing the plugin manager, translating the plugin declarations, and adopting a cleaner structure.

If you follow the order in this article, you can make that move without losing your setup. First remove packer-specific bootstrapping and compile behavior. Then install lazy.nvim with a small structured layout. Then translate one plugin spec at a time. Finally, use :Lazy sync, :Lazy health, :Lazy profile, and lazy-lock.json to verify that the new system is working the way you expect.

That is the real goal. Not just “switch plugin managers,” but end up with a Neovim config that is easier to maintain, easier to reason about, and better aligned with the current plugin ecosystem.

Comments