Skip to main content
The vim module is Neovim’s Lua “standard library” - a set of core functions and variables that are always available. It provides access to Vimscript functions, variables, commands, and options through a Lua interface.

Module Overview

The vim module is automatically loaded - no require("vim") needed.
-- Inspect what's available
vim.print(vim)

Core Functions

vim.cmd()

Executes Vimscript commands from Lua.
command
string|table
required
Command(s) to execute. String form supports multiline Vimscript.
-- Single command
vim.cmd('echo "Hello, Nvim!"')

-- Multiline script
vim.cmd([[
  augroup my_group
    autocmd!
    autocmd FileType lua setlocal shiftwidth=2
  augroup END
]])

-- Table form (structured)
vim.cmd { cmd = 'write', args = { 'myfile.txt' }, bang = true }

-- Indexed form
vim.cmd.echo('"foo"')
vim.cmd.write { 'myfile.txt', bang = true }

vim.print()

Pretty-prints Lua values with syntax highlighting.
local config = { timeout = 1000, features = { 'lsp', 'treesitter' } }
vim.print(config)
-- {
--   features = { "lsp", "treesitter" },
--   timeout = 1000
-- }

-- Chain with other operations
local hl = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))

vim.inspect()

Converts any Lua value to a human-readable string.
local data = { a = 1, b = { c = 2 } }
local str = vim.inspect(data)
print(str)  -- "{ a = 1, b = { c = 2 } }"

vim.schedule()

Defers function execution to the main event loop.
fn
function
required
Function to schedule for execution
nil|string
tuple
Returns nil on success, error message on failure
vim.schedule(function()
  -- Safe to call API functions here
  vim.api.nvim_command('echom "Scheduled!"')
end)
Use vim.schedule() to safely call API functions from callbacks like timers or libuv events.

vim.schedule_wrap()

Returns a wrapped version of a function that will be called via vim.schedule().
local timer = vim.uv.new_timer()
timer:start(1000, 0, vim.schedule_wrap(function()
  vim.api.nvim_command('echom "Timer fired!"')
end))

Table Utilities

vim.deepcopy()

Creates a deep copy of a table.
orig
table
required
Table to copy
noref
boolean
When true, tables are never reused (no cycle detection). Default: false
local original = { a = 1, nested = { b = 2 } }
local copy = vim.deepcopy(original)
copy.nested.b = 3

vim.print(original.nested.b)  -- 2 (unchanged)
vim.print(copy.nested.b)      -- 3

vim.tbl_extend()

Merges multiple tables.
local defaults = { timeout = 1000, retries = 3 }
local user_config = { timeout = 2000 }

local config = vim.tbl_extend('force', defaults, user_config)
vim.print(config)  -- { timeout = 2000, retries = 3 }
-- Last value wins
local result = vim.tbl_extend('force', { a = 1 }, { a = 2 })
-- result = { a = 2 }

vim.tbl_deep_extend()

Recursively merges tables.
local base = { ui = { width = 80, height = 24 } }
local overrides = { ui = { width = 120 } }

local config = vim.tbl_deep_extend('force', base, overrides)
vim.print(config)
-- { ui = { width = 120, height = 24 } }

vim.tbl_keys() / vim.tbl_values()

local t = { a = 1, b = 2, c = 3 }
vim.print(vim.tbl_keys(t))    -- { "a", "b", "c" }
vim.print(vim.tbl_values(t))  -- { 1, 2, 3 }

vim.tbl_map() / vim.tbl_filter()

-- Map: transform values
local doubled = vim.tbl_map(function(v) return v * 2 end, { 1, 2, 3 })
vim.print(doubled)  -- { 2, 4, 6 }

-- Filter: keep only matching values
local evens = vim.tbl_filter(function(v) return v % 2 == 0 end, { 1, 2, 3, 4 })
vim.print(evens)  -- { 2, 4 }

vim.tbl_contains() / vim.list_contains()

-- Check if table contains value
vim.tbl_contains({ 1, 2, 3 }, 2)  -- true
vim.list_contains({ 'a', 'b' }, 'c')  -- false

-- With predicate
vim.tbl_contains({ { id = 1 }, { id = 2 } }, function(v)
  return v.id == 2
end, { predicate = true })  -- true

vim.tbl_isempty() / vim.islist() / vim.isarray()

vim.tbl_isempty({})          -- true
vim.tbl_isempty({ a = 1 })   -- false

vim.islist({ 1, 2, 3 })      -- true (contiguous integers from 1)
vim.islist({ [1] = 'a', [3] = 'c' })  -- false (has gap)

vim.isarray({ [1] = 'a', [5] = 'b' })  -- true (integer keys)
vim.isarray({ a = 1 })                  -- false (string key)

String Utilities

vim.split() / vim.gsplit()

-- Eager split (returns table)
local parts = vim.split('a:b:c', ':')
vim.print(parts)  -- { "a", "b", "c" }

-- Lazy split (returns iterator)
for part in vim.gsplit('a:b:c', ':') do
  print(part)
end

-- With options
vim.split('a::b:', ':', { trimempty = true })  -- { "a", "b" }
vim.split('a.b.c', '.', { plain = true })      -- { "a", "b", "c" }

vim.trim() / vim.startswith() / vim.endswith()

vim.trim('  hello  ')           -- "hello"
vim.startswith('hello', 'hel')  -- true
vim.endswith('world', 'ld')     -- true

Validation

vim.validate()

Validates function arguments with helpful error messages.
function my_function(name, age)
  vim.validate('name', name, 'string')
  vim.validate('age', age, 'number')
  -- Function body
end

my_function('Alice', 25)  -- OK
my_function(123, 25)      -- Error: name: expected string, got number

-- Multiple types allowed
vim.validate('id', value, { 'number', 'string' })

-- Custom validator
vim.validate('port', 8080, function(v)
  return v > 0 and v < 65536
end, 'valid port number')

-- Optional parameters
vim.validate('config', nil, 'table', true)  -- OK (optional)

Special Values

vim.NIL

Represents null in RPC/JSON contexts.
local t = { key = vim.NIL }
-- Converts to JSON: {"key": null}

vim.empty_dict()

Creates an explicitly empty dictionary (vs. empty array).
local empty_dict = vim.empty_dict()
local empty_array = {}

-- When passed to Vimscript:
-- empty_dict -> {}
-- empty_array -> []

Utility Functions

vim.notify()

Displays notifications to the user.
vim.notify('Operation completed', vim.log.levels.INFO)
vim.notify('Warning: file modified', vim.log.levels.WARN)
vim.notify('Error occurred', vim.log.levels.ERROR)

-- With options
vim.notify('Custom notification', vim.log.levels.INFO, {
  title = 'My Plugin'
})

vim.defer_fn()

Executes a function after a delay.
vim.defer_fn(function()
  print('Executed after 1 second')
end, 1000)

vim.wait()

Waits for a condition or timeout.
-- Wait up to 5000ms for condition
local ok = vim.wait(5000, function()
  return vim.g.ready == true
end, 100)  -- check every 100ms

if ok then
  print('Condition met')
else
  print('Timed out')
end

Type Checking

vim.is_callable()

vim.is_callable(function() end)  -- true
vim.is_callable(print)           -- true
vim.is_callable('string')        -- false

-- Works with __call metamethod
local t = setmetatable({}, { __call = function() end })
vim.is_callable(t)  -- true

Miscellaneous

vim.log.levels

Log level constants for notifications.
vim.log.levels.TRACE  -- 0
vim.log.levels.DEBUG  -- 1
vim.log.levels.INFO   -- 2
vim.log.levels.WARN   -- 3
vim.log.levels.ERROR  -- 4
vim.log.levels.OFF    -- 5

vim.iconv()

Converts text between character encodings.
local utf8_text = vim.iconv('text', 'latin1', 'utf-8')

See Also

Build docs developers (and LLMs) love