Neovim’s Built‑In LSP Client: Why It’s the New Standard for Language Features
Neovim’s core LSP client replaces external plugins, offering a minimal, stable foundation for language features. Learn how to configure it, see a concrete Python example, and understand the trade‑offs with richer plugins.
03 May 2026, 17:44 UTC

Problem: Too Many LSP Plugins, Too Few Consistency
When you started customizing Neovim, you probably discovered a jungle of language‑server‑related plugins: coc.nvim, LanguageClient-neovim, nvim-lspconfig, mason.nvim, and more. Each of them wraps the same core LSP protocol but adds its own quirks, extensions, and sometimes even a separate runtime. The result? A fragile setup that breaks when you upgrade Neovim, a duplicated effort for simple diagnostics, and a hard‑to‑maintain dotfile that lags behind the core editor.
Neovim 0.5+ shipped a full LSP client in its core (vim.lsp). That decision changed the game: it removes the need for a separate plugin to get basic language features, gives the editor full lifecycle control over servers, and standardises the API for diagnostics, hover, signature help, and code actions.
Thesis: The Core LSP Client is the Minimal, Extensible Foundation for Modern Neovim
By using the built‑in client you gain a lean, stable foundation that can be layered with any UI or extra features you want. The trade‑off is that the client intentionally keeps the feature set small, so you still need to decide whether you want a richer feature set from a plugin or to extend the core yourself.
1. Overview of the Built‑In LSP Client
The client lives in the vim.lsp module. It starts language servers as child processes, talks to them over JSON‑RPC (std‑io or TCP), and exposes the LSP protocol via Lua functions. The API is split into two parts:
- Configuration API – declare which servers to start and how.
- – interact with the server (e.g.,
vim.lsp.buf.definition(),vim.diagnostic.open_float()).
Because the client is part of Neovim’s core, you don’t need to install an external runtime. All you need is the language server binary, which you can manage with Mason or your system package manager.
2. Declarative Configuration: vim.lsp.config vs. vim.lsp.start_client
From 0.11 onward, Neovim introduced a declarative API: vim.lsp.config. Earlier versions used vim.lsp.start_client. Both let you specify the command, root directory, filetypes, and settings for a server.
-- Example: Python server with pyright
vim.lsp.config.setup({
name = "pyright",
cmd = {"pyright-langserver", "--stdio"},
filetypes = {"python"},
root_dir = function(fname)
return vim.fs.find({".git", "pyproject.toml", "setup.cfg"}, {path = vim.fn.fnamemodify(fname, ':p:h'), upward = true})[1]
end,
settings = {
python = {
analysis = {
typeCheckingMode = "basic",
},
},
},
})
Key points:
cmdis a table of the executable and arguments.- Root directory detection uses
vim.fs.findor a custom function; this is crucial for monorepos. - Settings are passed to the server via the
initializerequest.
If you’re on 0.10 or earlier, replace vim.lsp.config.setup with vim.lsp.start_client and the same arguments. The newer API is cleaner and easier to maintain, but the old one still works for backward compatibility.
3. Practical Example: Getting Diagnostics for a Python Project
Assume you have pyright installed via Mason and a Python project with a pyproject.toml. The steps are:
- Install the server:
:MasonInstall pyright. - Add the configuration above to your
init.lua(or.vimrcif you prefer Vimscript). - Open a
.pyfile. The client will automatically startpyright-langserverwith the correct root. - Run
:LspInfoto see the client in the list and confirm it’s attached to the file. - Hover over a name to see the tooltip, press
gd(mapped tovim.lsp.buf.definition()) to jump to the definition, and use:Trouble diagnosticsto view all issues in a sidebar.
To verify that diagnostics are working, intentionally add a typo like pritn and watch the red squiggle appear. The diagnostic information is exposed through vim.diagnostic, so you can also use vim.diagnostic.open_float() or vim.diagnostic.show_line_diagnostics() for custom UI.
4. Trade‑Offs and Limitations
The core client is intentionally minimal. Compared to coc.nvim or LanguageClient-neovim, it lacks:
- Built‑in support for LSP extensions such as inline values or semantic tokens (partial support added in 0.10).
- An extension host that can run arbitrary JavaScript/TypeScript code to add new features.
- Automatic server discovery or auto‑install; you must install the server yourself.
These gaps mean you may need an additional plugin if you want advanced UI (e.g., fuzzy code actions) or if your language server offers features that the core client doesn’t yet expose. However, the core client’s simplicity also means fewer moving parts and a lower chance of breaking on Neovim updates.
5. Actionable Next Steps
- Upgrade to Neovim 0.11+ if you haven’t already, and migrate your
vim.lsp.start_clientcalls tovim.lsp.config.setupfor cleaner syntax. - Use
Masonor your package manager to install the language servers you need. The client will pick them up automatically. - If you need advanced features, consider layering a lightweight UI plugin like
trouble.nvimorfidget.nvimon top of the core client. - Test your configuration with
:LspInfoand apyrightexample before committing to your dotfile. - Keep an eye on the Neovim release notes for future LSP enhancements—semantic token support is improving, and the API is stabilising.
By adopting the built‑in LSP client, you simplify your Neovim setup, reduce dependency noise, and keep your editor future‑proof while still having the option to extend it with UI plugins when you need more than the core provides.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.