github github
stars 216
issues 1
subscribers 5
forks 9



3 months ago


Spiritual successor of https://github.com/wbthomason/packer.nvim

🚧 WIP 🚧

Main differences to pckr.nvim:

  • Heavily refactored
  • Lockfile support
  • No compilation

Table of Contents

  1. Features
  2. Requirements
  3. Quickstart
  4. Example
  5. Commands
  6. Usage
    1. The setup and add functions
    2. Custom Initialization
    3. Specifying Plugins
    4. Performing plugin management operations
  7. Debugging


  • Declarative plugin specification
  • Support for dependencies
  • Extensible
  • Post-install/update hooks
  • Support for git tags, branches, revisions
  • Support for local plugins
  • Lockfile support


  • You need to be running Neovim v0.9 or newer
  • If you are on Windows 10, you need developer mode enabled in order to use local plugins (creating symbolic links requires admin privileges on Windows - credit to @TimUntersberger for this note)


If you want to automatically install and set up pckr.nvim on any machine you clone your configuration to, add the following snippet somewhere in your config before your first usage of pckr:

local function bootstrap_pckr()
  local pckr_path = vim.fn.stdpath("data") .. "/pckr/pckr.nvim"

  if not vim.loop.fs_stat(pckr_path) then



  -- My plugins here
  -- 'foo1/bar1.nvim';
  -- 'foo2/bar2.nvim';


-- This file can be loaded by calling `lua require('plugins')` from your init.vim

local cmd = require('pckr.loader.cmd')
local keys = require('pckr.loader.keys')

  -- Simple plugins can be specified as strings

  -- Lazy loading:
  -- Load on a specific command
    cond = {

  -- Load on specific keymap
  {'tpope/vim-commentary', cond = keys('n', 'gc') },

  -- Load on specific commands
  -- Also run code after load (see the "config" key)
  { 'w0rp/ale',
    cond = cmd('ALEEnable'),
    config = function()

  -- Local plugins can be included

  -- Plugins can have post-install/update hooks
  {'iamcco/markdown-preview.nvim', run = 'cd app && yarn install', cond = cmd('MarkdownPreview')};

  -- Post-install/update hook with neovim command
  { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' };

  -- Post-install/update hook with call of vimscript function with argument
  { 'glacambre/firenvim', run = function()
  end };

  -- Use specific branch, dependency and run lua file after load
  { 'glepnir/galaxyline.nvim',
    branch = 'main',
    requires = {'kyazdani42/nvim-web-devicons'},
    config = function()

  -- Run config *before* the plugin is loaded
  {'whatyouhide/vim-lengthmatters', config_pre = function()
    vim.g.lengthmatters_highlight_one_column = 1
    vim.g.lengthmatters_excluded = {'pckr'}


pckr provides the following commands.

" Remove any disabled or unused plugins
:Pckr clean

" Install missing plugins
:Pckr install [plugin]+

" Update installed plugins
:Pckr update [plugin]+

" Upgrade pckr.nvim
:Pckr upgrade

" Clean, install, update and upgrade
:Pckr sync [plugin]+

" View status of plugins
:Pckr status

" Create a lockfile of plugins with their current commits
:Pckr lock

" Restore plugins using saved lockfile
:Pckr restore


The following is a more in-depth explanation of pckr's features and use.

The setup and add functions

pckr providespckr.add(spec), which is used in the above examples where spec is a table specifying a single or multiple plugins.

Custom Initialization

pckr.setup() can be used to provide custom configuration (note that this is optional). The default configuration values (and structure of the configuration table) are:

  package_root        = util.join_paths(vim.fn.stdpath('data'), 'site', 'pack'),
  max_jobs            = nil, -- Limit the number of simultaneous jobs. nil means no limit
  autoremove          = false, -- Remove unused plugins
  autoinstall         = true, -- Auto install plugins
  git = {
    cmd = 'git', -- The base command for git operations
    depth = 1, -- Git clone depth
    clone_timeout = 60, -- Timeout, in seconds, for git clones
    default_url_format = 'https://github.com/%s' -- Lua format string used for "aaa/bbb" style plugins
  log = { level = 'warn' }, -- The default print log level. One of: "trace", "debug", "info", "warn", "error", "fatal".
  opt_dir = ...,
  start_dir = ...,
  lockfile = {
    path = util.join_paths(vim.fn.stdpath('config', 'pckr', 'lockfile.lua'))

Specifying plugins

pckr is based around declarative specification of plugins.

  1. Absolute paths to a local plugin
  2. Full URLs (treated as plugins managed with git)
  3. username/repo paths (treated as Github git plugins)

Plugin specs can take two forms:

  1. A list of plugin specifications (strings or tables)
  2. A table specifying a single plugin. It must have a plugin location string as its first element, and may additionally have a number of optional keyword elements, shown below:
  'myusername/example',    -- The plugin location string

  -- The following keys are all optional

  -- Specifies a git branch to use
  branch: string?,

  -- Specifies a git tag to use. Supports '*' for "latest tag"
  tag: string?,

  -- Specifies a git commit to use
  commit: string?,

  -- Skip updating this plugin in updates/syncs. Still cleans.
  lock: boolean?,

  -- Post-update/install hook. See "update/install hooks".
  run: string|function,

  -- Specifies plugin dependencies. See "dependencies".
  requires: string|string[],

  -- Specifies code to run after this plugin is loaded. If string then require it.
  -- E.g:
  --   config = function() require('mod') end
  -- is equivalent to:
  --   config = 'mod'
  config: string|function,

  -- Specifies code to run before this plugin is loaded. If string then require it.
  config_pre: string|function,

  cond: function|function[],    -- Specifies custom loader

Update/install hooks

You may specify operations to be run after successful installs/updates of a plugin with the run key. This key may either be a Lua function, which will be called with the plugin table for this plugin (containing the information passed to the spec as well as output from the installation/update commands, the installation path of the plugin, etc.), a string, or a table of functions and strings.

If an element of run is a string, then either:

  1. If the first character of run is ":", it is treated as a Neovim command and executed.
  2. Otherwise, run is treated as a shell command and run in the installation directory of the plugin via $SHELL -c '<run>'.


Plugins may specify dependencies via the requires key. This key can be a string or a list (table).

If requires is a string, it is treated as specifying a single plugin. If a plugin with the name given in requires is already known in the managed set, nothing happens. Otherwise, the string is treated as a plugin location string and the corresponding plugin is added to the managed set.

If requires is a list, it is treated as a list of plugin specifications following the format given above.

Plugins specified in requires are removed when no active plugins require them.

🚧 TODO: explain that plugins can only be specified as a table once.

Custom loader

A custom loader for a plugin may be specified via cond. This is a function which has a function as its first argument. When this function argument is called, the plugin is loaded.

For example, the following plugin is lazy-loaded on the key mapping ga:

  {"my/plugin", cond = function(load_plugin)
    vim.keymap.set('n', 'ga', function()
      vim.keymap.del('n', 'ga')

  -- equivalent to --

local keys = require('pckr.loader.keys')
  {"my/plugin", cond = keys('n', 'ga') },

Automatically find local plugins

This snippet can be used to automatically detect local plugins in a particular directory.

local local_plugin_dir = vim.env.HOME..'/projects/'

local function resolve(x)
  if type(x) == 'string' and x:sub(1, 1) ~= '/' then
    local name = vim.split(x, '/')[2]
    local loc_install = vim.fs.join_paths(local_plugin_dir, name)
    if name ~= '' and vim.fn.isdirectory(loc_install) == 1 then
      return loc_install

local function try_get_local(spec)
  if type(spec) == 'string' then
    return resolve(spec) or spec

  if not spec or type(spec[1]) ~= 'string' then
    return spec

  return resolve(spec[1]) or spec[1]

local function walk_spec(spec, field, fn)
  if type(spec[field]) == 'table' then
    for j in ipairs(spec[field]) do
      walk_spec(spec[field], j, fn)
    walk_spec(spec[field], 'requires', fn)
  spec[field] = fn(spec[field])

local init {
  -- plugins spec

walk_spec({init}, 1, try_get_local)



pckr.nvim logs to stdpath(cache)/pckr.nvim.log. Looking at this file is usually a good start if something isn't working as expected.