Neovim 0.12.0 was released on March 29, 2026. This release adds a built-in plugin manager, expands built-in LSP support, introduces native insert-mode auto-completion, improves the default UI and statusline, adds several useful Lua APIs, and includes a number of breaking changes that may require config updates.
This guide is organized around the changes that matter most in day-to-day use, with practical examples and a short migration checklist at the end.
The most important additions in 0.12 are:
Built-in plugin management with
vim.packExpanded built-in LSP support, including
selectionRange,inlineCompletion,linkedEditingRange,documentLink, and viewport-only semantic tokensNative insert-mode auto-completion via the new
'autocomplete'optionA redesigned experimental core UI (
ui2)New commands such as
:restart,:connect, and:uniqUseful new Lua APIs such as
vim.net.request(),vim.fs.ext(),vim.list.unique(), andvim.list.bisect()Built-in plugins such as
:Undotreeand:DiffToolBreaking changes around diagnostics, Treesitter behavior, and renamed APIs
Official source: :help news-0.12.
Neovim 0.12 adds vim.pack, a built-in plugin manager. It is still marked experimental, but the official docs describe it as stable enough for daily use.
vim.pack.add({
'https://github.com/sainnhe/gruvbox-material',
'https://github.com/nvim-treesitter/nvim-treesitter',
{
src = 'https://github.com/neovim/nvim-lspconfig',
version = 'v2.0.0',
},
})You can also use a table to set the plugin name explicitly:
vim.pack.add({
{
src = 'https://github.com/user/generic-name',
name = 'my-plugin',
},
})vim.pack manages plugins in a dedicated directory under your data path:
site/pack/core/optIt also maintains a lockfile at:
~/.config/nvim/nvim-pack-lock.jsonThat lockfile should be treated like part of your config and kept under version control.
vim.pack.update()By default, this opens a confirmation buffer where you can review pending changes.
Useful variants:
vim.pack.update({ 'nvim-treesitter' })
vim.pack.update(nil, { force = true })
vim.pack.update(nil, { offline = true })
vim.pack.update(nil, { target = 'lockfile' })vim.pack.del({ 'old-plugin' })vim.packuses Git and expects plugin sources to be Git repositories.It is not a lazy-loading framework.
During
init.luasourcing, plugin loading defaults differently than after startup, so read:help vim.packif you rely on exact loading behavior.If a plugin is already on disk,
vim.pack.add()does not automatically verify that its current revision matches the declaredversion; usevim.pack.update()to synchronize.
PackChangedPre and PackChanged can be used for post-install or post-update hooks:
vim.api.nvim_create_autocmd('PackChanged', {
callback = function(ev)
if ev.data.spec.name == 'nvim-treesitter' then
vim.system({ 'make' }, { cwd = ev.data.path })
end
end,
})Neovim 0.12 expands built-in LSP support significantly.
The recommended built-in pattern is:
vim.lsp.config['lua_ls'] = {
cmd = { 'lua-language-server' },
filetypes = { 'lua' },
root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' },
}
vim.lsp.enable('lua_ls')You can check whether it is attached with:
:checkhealth vim.lspNeovim now provides these global LSP mappings by default:
gra→ code actionsgri→ implementationsgrn→ renamegrr→ referencesgrt→ type definitiongrx→ run codelensgO→ document symbolsCtrl-Sin Insert mode → signature help
gx also handles textDocument/documentLink when the server supports it.
0.12 adds or improves support for:
textDocument/selectionRangetextDocument/inlineCompletiontextDocument/linkedEditingRangetextDocument/documentLinktextDocument/onTypeFormattingtextDocument/semanticTokens/rangefor viewport-only semantic tokensworkspace/codeLens/refresh
If Treesitter is not active, visual-mode an / in selection falls back to LSP selectionRange.
The docs explicitly note that some features may need to be enabled manually depending on your setup, including:
code lens
linked editing
inlay hints
inline completion
Neovim 0.12 includes a built-in :lsp command for interactive client management.
Neovim 0.12 adds the new 'autocomplete' option:
vim.o.autocomplete = trueThis enables built-in insert-mode auto-completion without a third-party completion UI plugin.
'complete' gained new flags:
F{func}→ complete using a named functionF→ complete using'completefunc'o→ complete using'omnifunc'
It also supports limiting matches per source:
vim.o.complete = ".^5,t^3,w"Useful popup menu options:
vim.o.pumborder = 'rounded'
vim.o.pummaxwidth = 40
vim.o.completeopt = 'menu,menuone,noselect'Note that nearest is now a valid completeopt behavior for proximity-based sorting.
Neovim 0.12 introduces ui2, an experimental redesign of the core messages and command-line UI.
Enable it with:
require('vim._core.ui2').enable()What it changes:
avoids many “Press ENTER” interruptions
avoids delays from warnings like W10
highlights the command line as you type
exposes the pager as a regular buffer + window
Because it is explicitly marked experimental, it is best treated as something to try, not something to assume is stable in every workflow yet.
:restartThis restarts Neovim and reattaches all UIs.
:connect /tmp/nvim.sockThis dynamically connects the current UI to a server address.
The 'busy' buffer status is shown in the default statusline using the symbol ◐.
The default statusline is more useful in 0.12 and can show:
diagnostic counts
progress/status information
busy state
terminal exit information
Two notable helpers are:
vim.diagnostic.status()
vim.lsp.status()If you already maintain a custom statusline, these APIs make it easier to integrate built-in diagnostics and LSP progress.
Neovim now includes a built-in HTTP helper:
local res = vim.net.request('https://api.github.com/repos/neovim/neovim')
print(res.body)print(vim.fs.ext('init.lua')) -- lua
print(vim.fs.ext('archive.tar.gz')) -- gzlocal deduped = vim.list.unique({ 1, 2, 2, 3, 1 })local idx = vim.list.bisect({ 10, 20, 30, 40 }, 25)vim.hl.range()now allows multiple timed highlightsvim.tbl_extend()andvim.tbl_deep_extend()can take a function as conflict behaviorvim.version.intersect()computes version-range intersectionsIter:take()andIter:skip()can accept predicatesIter:peek()works for all iterator typesIter:unique()deduplicates iterator output
:packadd nvim.undotree
:UndotreeThis adds a built-in visual undo-tree navigator.
:packadd nvim.difftool
:DiffTool path/to/dir1 path/to/dir2This compares directories and files.
:packadd nvim.tohtml
:TOhtmltohtml is no longer loaded by default.
Treesitter-based Markdown highlighting is enabled by default.
Neovim 0.12 adds built-in node-based selection:
vthenan→ select outwardvthenin→ select inwardvthen]n→ next siblingvthen[n→ previous sibling
This is especially useful if you want structural selection without relying on extra plugins.
vim.treesitter.get_parser() no longer throws on failure. It now returns nil when it cannot create a parser.
Old assumption:
local parser = vim.treesitter.get_parser(bufnr)
Safer 0.12 pattern:
local parser = vim.treesitter.get_parser(bufnr)
if parser then
-- use parser
endDiagnostic signs can no longer be configured with :sign-define or sign_define().
You must configure them through vim.diagnostic.config().
Example:
local sev = vim.diagnostic.severity
vim.diagnostic.config({
severity_sort = true,
signs = {
text = {
[sev.ERROR] = 'E',
[sev.WARN] = 'W',
[sev.INFO] = 'I',
[sev.HINT] = 'H',
},
},
})vim.diagnostic.disable()andvim.diagnostic.is_disabled()were removedthe legacy signature of
vim.diagnostic.enable()is no longer supportedvim.diagnostic.status()is useful for statuslines
:restart→ restart Neovim and reattach UIs:connect {addr}→ connect current UI to a server:iput→ like:put, but adjusts indent:retab -indentonly→ retab only leading whitespace:uniq→ deduplicate lines in the current buffer:wall ++p→ write all buffers and create missing parent directories:help!→ help lookup with smarter guessing
When a selected range contains a Lua code block, Neovim can detect and execute it as Lua.
'wildchar' now enables completion in search contexts such as /, ?, :g, :v, and :vimgrep.
Some defaults changed in 0.12:
'shelltemp'now defaults tofalse'diffopt'now includes improved defaults such asindent-heuristicandinline:char'shada'excludes/tmp/and/private/paths by defaultterminal
'scrollback'maximum increased to 1,000,000
Ctrl-R in Insert mode now inserts register contents literally by default.
If you relied on the older behavior that interpreted special characters, you may need to adjust your workflow.
Neovim 0.12 improves terminal behavior in several ways:
support for synchronized output (DEC private mode 2026)
support for clearing terminal scrollback via
CSI 3 Jnvim_open_term()can now be called on a non-empty buffersuspended PTY processes are shown as
[Process suspended]and can be resumed by keypress
Examples include:
DiffTextAddOkMsgStderrMsgStdoutMsgSnippetTabstopActivePmenuBorderPmenuShadow
Examples include:
CmdlineLeavePreMarkSetSessionLoadPreTabClosedPrePackChangedPrePackChanged
These are the items most likely to affect existing configs or plugins.
vim.diff→vim.text.diffsemantic tokens
start()/stop()usage moved toenable()
remove any
:sign-define-based diagnostic sign setupmove diagnostic sign configuration to
vim.diagnostic.config()stop using removed functions like
vim.diagnostic.disable()
update code that assumes
vim.treesitter.get_parser()throws on failure
tohtmlis now opt-inold shellmenu plugin is removed
Python 3.7 and 3.8 support were dropped
'shelltemp'changed tofalseInsert-mode
Ctrl-Rnow inserts register contents literally
~/.config/nvim/
├── init.lua
├── nvim-pack-lock.json
└── lua/
└── config/
├── init.lua
├── options.lua
├── keymaps.lua
├── diagnostics.lua
├── autocmds.lua
└── pack.luarequire('config')require('config.options')
require('config.pack')
require('config.keymaps')
require('config.diagnostics')
require('config.autocmds')vim.o.autocomplete = true
vim.o.pumborder = 'rounded'
vim.o.pummaxwidth = 40
vim.o.completeopt = 'menu,menuone,noselect,nearest'local sev = vim.diagnostic.severity
vim.diagnostic.config({
severity_sort = true,
update_in_insert = false,
float = {
border = 'rounded',
source = true,
},
signs = {
text = {
[sev.ERROR] = 'E',
[sev.WARN] = 'W',
[sev.INFO] = 'I',
[sev.HINT] = 'H',
},
},
})vim.pack.add({
'https://github.com/sainnhe/gruvbox-material',
'https://github.com/nvim-treesitter/nvim-treesitter',
})After upgrading to 0.12, you may be able to simplify your setup by removing or rethinking:
custom diagnostic-sign definitions based on
:sign-definemanual statusline diagnostic counters
ad-hoc HTTP wrappers for simple requests
third-party undotree plugins if the built-in one is enough
plugin-manager bootstrap code if you switch to
vim.pack
Whether you should remove nvim-lspconfig or a third-party completion plugin depends on how much custom behavior you want. Neovim 0.12 reduces the need for them, but it does not automatically make them obsolete for every workflow.
Neovim 0.12 is a substantial release, but the most important story is not just new features. It is that more of the editor is now usable without a large stack of plugins.
The biggest practical wins are:
vim.packfor built-in plugin managementstronger built-in LSP support
native insert-mode auto-completion
cleaner diagnostics configuration
useful built-in UI and Lua improvements
If you upgrade, the first places to review are your plugin manager, diagnostic sign config, Treesitter parser assumptions, and any code that still uses renamed APIs.
:help news-0.12:help vim.pack:help lsp:help diagnostic-signs:help ui2:help deprecated-0.12

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.