# neoconf.nvim: moving Neovim LSP settings out of Lua and into JSON

> neoconf.nvim is a Lua plugin for Neovim that lets language server configuration live in JSON files, global and per project, with live reload into running clients. It suits people who want to reuse settings from VS Code or coc.nvim and keep project settings next to the code.

**folke/neoconf.nvim** — 💼 Neovim plugin to manage global and project-local settings

- Repository: https://github.com/folke/neoconf.nvim
- Stars: 961 · Forks: 42
- Language: Lua
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/folke-neoconf-nvim

## Language server settings that live next to the code

Neovim users tend to end up with LSP configuration scattered across a personal init.lua and a pile of per-project tweaks, and none of it can be shared with a colleague who uses a different editor. neoconf.nvim takes one slice of that problem, the language server settings, and moves it into JSON files that a project can carry in version control.

Two file names matter. The global file lives in your Neovim config directory as neoconf.json, and each project can carry its own local file named .neoconf.json, for example at ~/projects/foobar/.neoconf.json. The README describes the JSON files as being allowed to contain comments, which is why the default configuration sets a jsonc filetype for them.

The audience is Neovim users with several language servers in play and a preference for editable, discoverable configuration. The plugin also imports settings that already exist elsewhere, covering the local .vscode/settings.json of a VS Code project, the global or local coc-settings.json of coc.nvim, and the global or local JSON settings of nlsp-settings.nvim. That import path is what makes it more than a JSON to Lua bridge, since a team can keep a shared .vscode folder and have it read by Neovim too.

## Setup order is the first thing that can go wrong

The README is blunt about one requirement: neoconf.nvim has to be set up before nvim-lspconfig. That ordering is not cosmetic. The plugin has a default block called plugins.lspconfig with enabled = true, and that block is what pushes configuration into language server clients, so if lspconfig has already been initialised the clients exist before anything is sent to them.

Installation goes through your preferred package manager, and the documented example uses lazy.nvim:

```lua
{ "folke/neoconf.nvim" }
```

The setup call itself takes an optional table, and the README shows the shape with the usual ordering constraint:

```lua
require("neoconf").setup({
  -- override any of the default settings here
})

-- setup your lsp servers as usual
require("lspconfig").lua_ls.setup(...)
```

A Neovim of at least version 0.7.2 is required. There is also a hard dependency on the jsonc treesitter parser, because the default filetype_jsonc option exists precisely so that comments in the settings files can be handled, and the configuration warns that the parser has to be installed for that to work.

## Merging order decides which settings win

The default configuration documents the precedence rules directly, and they are worth reading before you fight a setting that seems to be ignored. The lspconfig plugin block configures language server clients with settings applied in a fixed order: the Lua settings passed in the lspconfig setup first, then the global JSON settings, then the local JSON settings.

That is the whole model in three lines. Lua is the base layer, the global neoconf.json is the next layer, and the project local .neoconf.json sits on top. A key you set in Lua can therefore be overridden globally, and overridden again locally, but a key you set only in Lua is never removed by the JSON files.

Live reload closes the loop. The live_reload option is on by default and sends new configuration to language server clients when the JSON settings change, which is the reason to use JSON files in the first place: editing a setting does not require reloading the editor or restarting the server.

The defaults also decide which settings files exist and what is imported:

```lua
  -- name of the local settings files
  local_settings = ".neoconf.json",
  -- name of the global settings file in your Neovim config directory
  global_settings = "neoconf.json",
  -- import existing settings from other plugins
  import = {
    vscode = true, -- local .vscode/settings.json
    coc = true, -- global/local coc-settings.json
    nlsp = true, -- global/local nlsp-settings.nvim json settings
  },
```

One setting here has an easy to miss dependency. filetype_jsonc defaults to true, so the settings files are treated as jsonc and comments are allowed, but only if the jsonc treesitter parser is installed. Without that parser the option is a promise the editor cannot keep.

## The :Neoconf command family and the two floating windows

Everything the plugin does day to day is reachable from one command. :Neoconf opens a UI to select one of the local or global JSON config files to edit, :Neoconf local narrows that to the local files, and :Neoconf global narrows it to the global ones. Two more commands are read only: :Neoconf show opens a floating window with the merged configuration, and :Neoconf lsp opens a floating window with the merged language server configuration.

Those two windows are the diagnostic tool of choice. When a setting does not behave as expected, the merged view tells you what the plugin actually resolved after the Lua, global and local layers were combined, and the LSP view tells you what was handed to the clients. Both are part of the command set rather than an add on, which suggests the author expected the merge order to be a common source of confusion.

The command takes no file arguments in the documented list. It presents a picker instead, so picking a settings file is a UI action rather than something you can script as :Neoconf some/path/file.json. If you need a specific file opened, the picker is the path the plugin offers.

## Completion in JSON files and annotations in Lua files

The plugin wires two language servers into its own editing experience. A jsonls block is configured to give completion in .neoconf.json files, and it ships with configured_servers_only = true, which restricts those completions to servers that are actually configured. That is a deliberate narrowing: a settings file for a server you do not run should not offer you that server's options.

A lua_ls block handles the opposite direction, completion of LSP server settings inside your Neovim Lua config files. Its default enabled_for_neovim_config = true limits lua_ls annotations to your Neovim config directory, while enabled = false keeps the explicit annotation switch off, with the comment noting it is mostly relevant in the local .neoconf.json file.

The exported types are usable in your own code. The README shows a table of server settings annotated with the lspconfig.options type, which is what gives the editor knowledge of the server specific tables:

```lua
  ---@type lspconfig.options
  local servers = {
    ansiblels = {},
    bashls = {},
    clangd = {},
  }
```

The same annotation mechanism is what allows the plugin to know what to offer as completions for settings it does not own, since the schema comes from the Lua type information rather than from a hand written JSON schema.

## An API for plugin authors, with a documentation wrinkle

The plugin exposes an API so other plugins can load global and local settings for their own configuration. Two pieces matter. A SettingsPlugin class carries a name, an optional setup function, an optional on_update handler and an on_schema callback. Neodev.register(plugin) registers a plugin, and the stated biggest use case is auto completion for that plugin inside the JSON settings files.

The schema callback receives a schema object and can import the Lua types of a plugin's default settings:

```lua
require("neoconf.plugins").register({
    on_schema = function(schema)
    schema:import("myplugin", defaults)
    end,
    })
```

The read side is Neoconf.get(key, defaults, opts), which returns the requested settings merged over an optional table of defaults. The options table, WorkspaceOptions, controls how the root directory is determined through a file, a buffer or the LSP root directory, and whether local and global settings are merged, both of which default to true.

There is a wrinkle worth flagging before you build on this. The API section is introduced as an API that Neodev comes with, the registration call is written as Neodev.register, but the worked example calls require("neoconf.plugins").register, and the settings key in that same example is read with Neoconf.get("neodev", defaults). The names have drifted apart in the documentation, so if you adopt the API, confirm the exact entry point in the source of the version you install rather than copying the examples literally.

The plugin also acknowledges json.lua, a pure Lua JSON library, as the parser used for jsonc files, which explains why a Lua environment can read JSON with comments in it at all.

## Apache-2.0 licence and a release tag that trails the commits

The project is licensed under Apache-2.0, a permissive licence with an explicit patent grant, which suits teams that want to ship configuration tooling inside commercial products. That is a licensing note, not advice, and you should still check the LICENSE file yourself if redistribution matters to you.

On maintenance, the last push was on 2026-10-17, and the repository is not archived, so the code is being changed close to the present. The released versions move more slowly. The most recent tagged release is v1.4.0 from 2025-01-24, with v1.3.3 from 2024-09-16 and v1.3.2 from 2024-07-18 behind it. So commits land between releases, and if you want a fixed point rather than a moving branch, the tags are the only stable reference points listed.

Two operational limits are worth carrying into any adoption decision. The plugin depends on Neovim 0.7.2 or newer and on the jsonc treesitter parser, so it is not usable in a minimal Neovim install without adding a parser. And the import options assume a specific layout: the VS Code import reads a local .vscode/settings.json, so a repository that keeps VS Code settings somewhere other than that path will not be picked up.

## Conclusion

neoconf.nvim fits Neovim users who want language server settings in shareable JSON files and who already keep a VS Code or coc.nvim configuration to import from. It does not fit people who want their LSP options to live entirely in Lua, since the JSON layer sits on top of lspconfig rather than replacing it. Verify first that your Neovim is at least 0.7.2, that the jsonc treesitter parser is installed so comments parse, and that you call require("neoconf").setup() before nvim-lspconfig loads.

## FAQ

### How do I install neoconf.nvim?

Install it with your preferred package manager. The README shows the lazy.nvim spec as { "folke/neoconf.nvim" }. The plugin requires Neovim 0.7.2 or higher.

### Why does neoconf.nvim have to be set up before nvim-lspconfig?

The README states it is important to set up neoconf.nvim before nvim-lspconfig. The reason sits in the default plugins.lspconfig block, which is enabled by default and is what configures language server clients with the merged settings.

### Can I put comments in the neoconf.json settings files?

Yes. The default filetype_jsonc option is true, which sets the filetype of settings files to jsonc so comments can be used, and the configuration notes that the jsonc treesitter parser has to be installed for this to work.

### Does neoconf.nvim import existing LSP settings from other editors and plugins?

Yes. The import options default to enabled for vscode, which reads the local .vscode/settings.json, coc, which reads global or local coc-settings.json, and nlsp, which reads global or local nlsp-settings.nvim JSON settings.

### Which setting wins if I define it in both my Lua config and a .neoconf.json file?

Local JSON settings are applied last, so a value in the project .neoconf.json overrides the same value in the global neoconf.json, which in turn overrides the Lua settings passed in the lspconfig setup.

## Sources

- [folke/neoconf.nvim on GitHub](https://github.com/folke/neoconf.nvim)
- [Issues](https://github.com/folke/neoconf.nvim/issues)
- [License: Apache-2.0](https://github.com/folke/neoconf.nvim/blob/main/LICENSE)
- [README](https://github.com/folke/neoconf.nvim/blob/main/README.md)
- [Releases](https://github.com/folke/neoconf.nvim/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/folke-neoconf-nvim
