Plugin Spec Reference

April 25, 2026 ยท View on GitHub

This document details all available options when defining plugin specifications.

Basic Syntax

Short Name

{ 'user/repo' }  -- Expands to https://github.com/user/repo

Full URL

{ src = 'https://github.com/user/repo' }

Local Path

{ src = '/path/to/local/plugin' }

Plugin Source Fields

FieldTypeDescription
[1]stringShort name (user/repo)
srcstringSource URL or local path
dirstringLocal directory (lazy.nvim compat)
urlstringGit URL (lazy.nvim compat)
namestringCustom plugin name

Version Control

FieldTypeDescription
versionstring|booleanBranch, tag, commit, or semver wildcard
versionRangeSemver range via vim.version.range()
sem_versionstringSemver (lazy.nvim compat)
branchstringGit branch
tagstringGit tag
commitstringGit commit hash

Version Examples

{ 'user/repo', version = 'main' }              -- Branch
{ 'user/repo', version = 'v1.0.0' }           -- Tag
{ 'user/repo', version = '1.*' }               -- Semver wildcard
{ 'user/repo', version = '^1.0.0' }            -- Semver range
{ 'user/repo', version = 'abc123' }            -- Commit
{ 'user/repo', version = false }               -- Disable semver (track default)
{ 'user/repo', version = vim.version.range('^1.0') }  -- Explicit Range object

Dependencies

FieldTypeDescription
dependenciesstring|string[]|Spec|tablePlugin dependencies
optionalbooleanOptional dependency flag

Dependencies Syntax

-- Simple string
{ 'user/repo', dependencies = 'dep/plugin' }

-- String array
{ 'user/repo', dependencies = { 'dep1', 'dep2' } }

-- lazy.nvim multi-string format
{ 'user/repo', dependencies = { 'dep1', { 'dep2', 'dep3' } } }

-- Full specs
{ 'user/repo', dependencies = {
  { 'dep/plugin', opts = {} },
} }

-- Optional dependency
{ 'user/repo', dependencies = {
  { 'optional/dep', optional = true },
} }

Loading Control

FieldTypeDescription
enabledboolean|functionEnable/disable plugin
condboolean|functionConditional loading
lazybooleanForce lazy loading
prioritynumberLoad priority (higher = earlier)

Loading Examples

-- Enable based on condition
{ 'user/repo', enabled = vim.fn.has('linux') == 1 }

-- Conditional loading
{ 'user/repo', cond = function()
  return vim.fn.filereadable('.project') == 1
end }

-- Force eager loading
{ 'user/repo', lazy = false }

-- Load priority (higher loads first)
{ 'user/repo', priority = 100 }

Development Mode

FieldTypeDescription
devbooleanEnable dev mode
dirstringLocal plugin path

Dev Mode Example

{ 'user/repo', dev = true, dir = '~/projects/plugin-name' }

Lazy Loading Triggers

Event Trigger

FieldTypeDescription
eventstring|string[]|tableNeovim events
{ 'user/repo', event = 'InsertEnter' }
{ 'user/repo', event = { 'InsertEnter', 'CmdlineEnter' } }
{ 'user/repo', event = 'BufReadPre *.lua' }
{ 'user/repo', event = 'VeryLazy' }  -- After UIEnter

Pattern Option

For event, you can specify a fallback pattern:

FieldTypeDescription
patternstringFallback autocmd pattern (default: *)
{ 'user/repo', event = 'BufReadPre', pattern = '*.lua' }

Command Trigger

FieldTypeDescription
cmdstring|string[]User commands
{ 'user/repo', cmd = 'MyCommand' }
{ 'user/repo', cmd = { 'Cmd1', 'Cmd2' } }

Keymap Trigger

FieldTypeDescription
keysstring|table|table[]Keymaps
-- Simple key
{ 'user/repo', keys = '<leader>f' }

-- With options
{ 'user/repo', keys = {
  { '<leader>f', function() require('plugin').action() end, desc = 'Action' },
  { '<leader>g', '<cmd>PluginCmd<cr>', mode = 'n', desc = 'Command' },
} }

Filetype Trigger

FieldTypeDescription
ftstring|string[]File types
{ 'user/repo', ft = 'lua' }
{ 'user/repo', ft = { 'lua', 'vim' } }

Hooks

FieldTypeDescription
initfunctionRuns before plugin loads
configfunction|booleanRuns after plugin loads
buildstring|functionRuns on install/update
optstableAuto-setup options

Hook Examples

-- Init hook (runs before load)
{ 'user/repo', init = function()
  vim.g.some_setting = true
end }

-- Config function
{ 'user/repo', config = function()
  require('plugin').setup({ option = 'value' })
end }

-- Auto-setup with opts
{ 'user/repo', opts = { option = 'value' } }

-- Build hook (string)
{ 'user/repo', build = 'make' }

-- Build hook (function)
{ 'user/repo', build = function()
  vim.fn.system({ 'make', '-C', plugin.path })
end }

Plugin Metadata

FieldTypeDescription
namestringCustom plugin name
mainstringExplicit main module (auto-detected if omitted)
{ 'user/repo', name = 'my-plugin' }
{ 'user/repo', main = 'plugin.main' }  -- Explicit main module

Automatic Main Module Detection

When using opts or config = true without specifying a main field, leanpack will automatically detect the main module. The explicit or auto-detected main module is also used to trigger require()-based lazy loading automatically! The detection process determines the module by:

  1. Scanning the plugin's lua/ directory for module folders
  2. Matching the plugin name against folder names (using normalization like lazy.nvim)
  3. Looking for init.lua files in matched folders

Example: For nvimtools/none-ls.nvim, leanpack auto-detects null-ls as the main module (since the internal folder is named null-ls), allowing you to use:

{
  'nvimtools/none-ls.nvim',
  opts = { sources = {} }  -- No main field needed!
}

This matches lazy.nvim's behavior where plugins work out of the box without manual configuration.

Complete Example

return {
  'nvim-telescope/telescope.nvim',
  cmd = 'Telescope',
  dependencies = {
    'nvim-lua/plenary.nvim',
    { 'nvim-tree/nvim-web-devicons', opts = {} },
  },
  opts = {
    defaults = {
      prompt_prefix = ' ',
    },
  },
  config = function()
    local telescope = require('telescope')
    telescope.setup(vim.tbl_deep_extend('force', telescope.loaded().telescope.opts, {
      defaults = {
        mappings = {
          n = { ['<c-t>'] = require('telescope._actions').select_tab },
        },
      },
    }))
  end,
}

Next Steps