Skip to main content

Overview

Neovim comes with Lua 5.1 built-in and always available. The Lua API provides a powerful, modern interface for configuring and extending Neovim. It consists of three complementary layers:

Vim API

Legacy Vimscript functions and Ex commands accessed via vim.fn and vim.cmd

Nvim API

C-level API functions for remote plugins and GUIs, accessed via vim.api

Lua API

Lua-specific standard library functions available through vim.*

Why Lua?

Lua is very simple and consistent. It has three fundamental mechanisms:
  • Tables: The universal data structure representing both lists and maps
  • Closures: Every scope (function, module, do block) is a closure and works the same way
  • Coroutines: Enable cooperative multithreading and generators
Neovim uses Lua 5.1 as the permanent interface. Plugins should target Lua 5.1 for compatibility. While Neovim is built with LuaJIT for performance, code should not assume LuaJIT-specific features like ffi are always available.

Running Lua Code

From the Command Line

Execute Lua directly from Nvim’s command line:
:lua print("Hello from Lua!")

:lua vim.print(package.loaded)

:lua =vim.version()  -- '=' prints the result

From Files

Execute Lua scripts from external files:
:luafile ~/config/script.lua
:source ~/config/script.lua  -- also works for .lua files

Configuration Files

Neovim supports init.lua as your main configuration file (placed in your config directory):
# Find your config directory
:echo stdpath('config')
# Typical locations:
# Linux:   ~/.config/nvim/
# macOS:   ~/.config/nvim/
# Windows: ~/AppData/Local/nvim/

Lua Modules

Place Lua modules in the lua/ directory within your ‘runtimepath’ to load them on demand:
~/.config/nvim/
├── init.lua
├── lua/
│   ├── mymodule.lua
│   └── config/
│       ├── init.lua
│       └── keymaps.lua
└── plugin/
-- Load mymodule.lua
require('mymodule')

-- Load config/keymaps.lua
require('config.keymaps')
-- or
require('config/keymaps')

-- Load config/init.lua directly
require('config')

Key Concepts

Error Handling

Lua functions may throw errors for exceptional failures. Use pcall() to handle them:
local ok, result = pcall(require, 'module_that_might_fail')
if not ok then
  print('Module failed to load: ' .. result)
else
  result.some_function()
end

Result-or-Message Pattern

Many Neovim Lua functions return (result|nil, error_message|nil) for expected failures:
-- If failure is expected, functions return nil + error message
local file, err = io.open('myfile.txt', 'r')
if not file then
  print('Error: ' .. err)
  return
end

-- Use assert() when you can't proceed on failure
local file = assert(io.open('required.txt', 'r'))

Truthy Values

Only false and nil are “falsy” in Lua. Everything else is “truthy”:
if 0 then print('0 is truthy') end          -- prints!
if '' then print('empty string is truthy') end  -- prints!
if {} then print('empty table is truthy') end   -- prints!

Iterators

An iterator is a function that returns the “next” value of a collection:
-- ipairs() iterates over list-style tables
local fruits = {'apple', 'banana', 'cherry'}
for i, fruit in ipairs(fruits) do
  print(i, fruit)
end

-- pairs() iterates over all table entries
local config = { width = 80, height = 24 }
for key, value in pairs(config) do
  print(key, value)
end

-- vim.iter provides functional iteration
vim.iter({ 1, 2, 3, 4 })
  :map(function(v) return v * 2 end)
  :filter(function(v) return v > 4 end)
  :totable()  -- { 6, 8 }

Function Call Syntax

Lua allows omitting parentheses when calling a function with a single string or table literal:
-- These are equivalent:
require('mymodule')
require 'mymodule'

-- Table literal syntax (common for "keyword arguments")
vim.keymap.set('n', '<leader>f', function()
  print('Hello')
end, { silent = true, desc = 'Print hello' })

-- Can omit parentheses:
vim.keymap.set('n', '<leader>f', function()
  print('Hello')
end, { silent = true })

Getting Help

Neovim’s built-in help system covers all Lua functionality:
:help lua-guide        " Practical Lua guide
:help lua.txt          " Complete Lua reference
:help vim.fn           " Vimscript functions from Lua
:help vim.api          " Nvim API functions
:help luaref           " Lua 5.1 language reference
To find documentation for a specific function:
:help vim.keymap.set()
:help vim.split()
:help vim.api.nvim_buf_set_lines()
Functions prefixed with underscore (e.g., vim._os_proc_children) are internal/private and should not be used by plugins.

Quick Example

Here’s a complete example showing common Lua API usage:
-- ~/.config/nvim/init.lua

-- Set options
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.expandtab = true
vim.opt.shiftwidth = 2

-- Set a global variable
vim.g.mapleader = ' '

-- Create a keymap
vim.keymap.set('n', '<leader>w', '<cmd>write<cr>', {
  desc = 'Save file'
})

-- Create an autocommand
vim.api.nvim_create_autocmd('TextYankPost', {
  desc = 'Highlight yanked text',
  callback = function()
    vim.highlight.on_yank()
  end,
})

-- Call a Vimscript function
local current_file = vim.fn.expand('%:p')
print('Editing: ' .. current_file)

-- Use the Nvim API
local buf = vim.api.nvim_get_current_buf()
local line_count = vim.api.nvim_buf_line_count(buf)
print('Buffer has ' .. line_count .. ' lines')

Next Steps

vim Namespace

Explore the vim.* global namespace and its submodules

Lua Concepts

Learn about Lua tables, closures, and coroutines

Performance Tips

-- Check if LuaJIT is available
if jit then
  -- Use LuaJIT-specific optimizations
  local ffi = require('ffi')
else
  -- Fallback for plain Lua 5.1
end

Build docs developers (and LLMs) love