In a plugin-heavy config, things break. A missing dependency, a bad require, an API change — your Neovim should tell you clearly and gracefully, not crash with cryptic messages.
Vimscript’s try/catch was the only option for years. But with Neovim 0.12’s mature Lua API, pcall + vim.notify is the new standard.
Let’s compare them head-to-head with real, copy-paste-ready examples.
Still works, but verbose, limited, and hard to debug.
" In a .vim file or legacy plugin
try
call luaeval('require("myplugin")')
echo "Plugin loaded successfully"
catch
echohl ErrorMsg
echo "Plugin failed: " . v:exception
echohl None
endtry
Clean, powerful, and integrated with Neovim’s notification system.
-- In init.lua or any Lua module
local ok, err = pcall(require, "myplugin")
if not ok then
vim.notify(
"Failed to load myplugin: " .. err,
vim.log.levels.ERROR,
{ title = "Neovim Config" }
)
-- Optional: write to log
vim.fn.writefile({err}, vim.fn.stdpath("log") .. "/config-errors.log", "a")
end
xpcall for full stack traces with a custom error handler.
| Feature | Vimscript try/catch | Lua pcall + vim.notify |
|---|---|---|
| Readability | Verbose & noisy | Clean & concise |
| Error details | v:exception only | Full error + stack trace |
| User notification | echohl + echo | Beautiful floating popup (vim.notify) |
| Logging | Manual only | Easy file + levels |
| Async / callbacks | Difficult | Native with xpcall |
| 2026 best practice | Avoid | Always use |
local function safe_require(name)
local ok, module = pcall(require, name)
if not ok then
vim.notify("Missing plugin: " .. name, vim.log.levels.WARN)
return nil
end
return module
end
local telescope = safe_require("telescope")
if telescope then
telescope.setup({})
end
try
lua require("telescope")
catch
echom "Missing plugin: telescope"
endtry
require() in plugin specs with safe_require — your config becomes crash-proof.vim.log.levels (ERROR, WARN, INFO) for beautiful, filterable notifications.stdpath("log") — perfect for debugging on remote servers.