CLI tool
lumen-oss/nvim-best-practices avatar
lumen-oss/nvim-best-practices

nvim-best-practices is a rulebook for writing Neovim plugins in Lua

Collection of DOs and DON'Ts for modern Neovim Lua plugin development

734 stars13 forksUnknownCC0-1.0

At a glance

What is it?
nvim-best-practices collects DOs and DON'Ts for Neovim plugin development in Lua, and the parts that upstreamed into Neovim itself, with concrete Lua examples for type annotations, scoped user commands and completion callbacks. The trade is that it is a guide, not a tool, and it is written against the Neovim 0.10.0 API.
Who is it for?
nvim-best-practices suits a developer writing or reviewing a Neovim plugin in Lua who wants the conventions a maintainer would ask for, and the type-safety and command-scoping sections are worth reading even if the rest is not your style. It does not suit someone looking for a linter or a scaffold, because the repository is a document with a README, a LICENSE and a .github directory, and nothing to run.
Can I use it commercially?
Yes. CC0-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 130 days ago.
What is it written in?
GitHub does not report a main language for this repository.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A document, not a linter, with a very specific audience

The repository is small in the way a policy document is small. The top level entries are .github/, LICENSE and README.md, and the primary language is listed as unknown because there is no code. The topic tags say best-practices, development, guide, lua, neovim, nvim, plugin and plugin-development, which describes the thing exactly. The README opens by promising DOs and DON'Ts for modern Neovim Lua plugin development, and it says right away that the code snippets in the document use the Neovim 0.10.0 API, which is the first constraint a reader has to accept. The intended user is a plugin author rather than a configuration author, and the audience is narrow enough that the guide can make firm claims instead of offering options.

Type annotations first, because Lua will not catch your mistakes

The first rule is about type safety, and the reasoning is stated up front. Lua, as a dynamically typed language, is great for configuration because it provides virtually immediate feedback, but for larger projects this can be a double-edged sword. The DON'T is to make your plugin susceptible to unexpected bugs at the wrong time, and the DO is to use LuaCATS annotations along with lua-language-server to catch potential bugs in your CI before your plugin's users do. The tools section names a GitHub action for type checking in CI, the lua-typecheck-action, plus lux-cli, which offers lx check for static type checking using emmylua-analyzer-rust and lx lint for linting with luacheck, along with emmylua-analyzer-rust, lua-language-server and luacheck for additional linting, and lazydev.nvim. For Nix users the guide points at nix-gen-luarc-json, which can be used to integrate with git-hooks.nix.

One command with subcommands beats a command per action

The second rule is about the command namespace, and the argument is about what the user sees. The DON'T is to pollute the command namespace with a command for each action, with the example being separate commands for install, prune, update and sync, because this can quickly become overwhelming when users rely on command completion. The DO is to gather subcommands under scoped commands and implement completions for each subcommand, so the same four actions become one command with four subcommands. The reasoning is short and concrete, and it is the kind of rule that changes how a plugin looks in its own help output. The guide also points at a Lua library, mega.cmdparse, which can take care of much of the boilerplate and comes with many features, and it is careful to note that the following example is a manual implementation.

Describing a subcommand with a LuaCATS class

The manual implementation starts by typing each subcommand, and the guide does that with an annotation rather than an ad hoc table. The idea is that a subcommand has an implementation, a function called when the subcommand is executed, and optional completions for the subcommand's arguments, and the type captures both:

lua
---@class MyCmdSubcommand
---@field impl fun(args:string[], opts: table) The command implementation
---@field complete? fun(subcmd_arg_lead: string): string[] (optional) Command completions callback, taking the lead of the subcommand's arguments

The optional completions field is the part that earns the type, since it is what lets the registration code check whether a subcommand has completions at all before calling into it, and what documents the callback signature for anyone reading the plugin later. The guide also notes that the parts of it have been upstreamed to Neovim, pointing at the lua-plugin help topic in Neovim nightly, so this material is not only commentary on someone else's conventions.

A table of subcommands, then a dispatcher that reads fargs

The next step is a table mapping subcommand keys to their implementations and completions, and the guide then builds a dispatcher function on top of it. The dispatcher reads the subcommand key from the first element of opts.fargs, slices off the remaining arguments with vim.list_slice, looks the subcommand up, and notifies the user with vim.notify at the error log level if the key is unknown, otherwise invoking the subcommand implementation with the arguments and the options. The whole thing is short, which is the point: the boilerplate is small enough to write by hand, and the guide shows it rather than hiding it behind the library it recommended earlier. The completion callback for a subcommand filters a candidate list against the argument the user has typed so far, using vim.iter with a filter and a totable, and the example list contains three real plugin names.

Registering the command and doing completion from the cmdline

The final piece is registration, and it is where the completion logic actually runs. The command is created with nvim_create_user_command with nargs set to plus, a description, a complete callback, and bang set to true if you want to support bang modifiers. The complete callback parses the command line to work out whether the user has typed the command with a subcommand or a subcommand with an argument, and in the first case it looks up the subcommand and calls its complete function, and in the second it filters the subcommand keys against what has been typed so far. The callback and the bang option in the middle of the block are:

lua
    bang = true, -- If you want to support ! modifiers
})

The comment in the snippet notes that the options will vary based on the use case, which is a fair warning, because a completion callback that mis-parses the command line will offer the wrong completions rather than failing loudly.

Keymaps: few of them, local where possible, and no setup DSL

The third rule set is about keymaps, and it is two DON'Ts. The first is to create excessive keymaps automatically, because doing so can conflict with user mappings, and the guide offers uncontroversial examples in a note, such as buffer-local mappings for specific file types, floating windows, or Plug mappings. The second is to define a fancy DSL for enabling keymaps through a setup function, and the reason given is direct, that you will have to implement and document the whole surface yourself. The repository's own philosophy is visible here. It prefers a small, predictable set of things a user can see, and it is happy to say that a feature is better left to the user rather than exposed through another layer of configuration.

A companion plugin template and a licence that lets you reuse it

Two supporting facts round out the repository. The first is that the guide points at an example built on it, @ColinKennedy's plugin template, which is the fastest way to see how the rules look applied to a whole project rather than in fragments. The second is the licence, which is CC0-1.0, a public domain dedication rather than a conventional open source licence with attribution requirements. For a document of this kind that is a sensible choice, since the point is that conventions get copied, and there is no attribution clause to satisfy when you paste the rules into your own plugin documentation. The licence file is at the root of the repository, next to the README and the .github directory, and there are no tagged releases, so the document has no versioned history to refer back to.

Editorial conclusion

nvim-best-practices suits a developer writing or reviewing a Neovim plugin in Lua who wants the conventions a maintainer would ask for, and the type-safety and command-scoping sections are worth reading even if the rest is not your style. It does not suit someone looking for a linter or a scaffold, because the repository is a document with a README, a LICENSE and a .github directory, and nothing to run. Before following it, check the API version it targets, since the code snippets use the Neovim 0.10.0 API, and note that the last push to the repository was on 2026-06-15 with no tagged releases.

Frequently asked questions

What does nvim-best-practices cover?

It is a collection of DOs and DON'Ts for modern Neovim Lua plugin development, and the visible sections cover type safety with LuaCATS annotations and lua-language-server, scoping user commands under subcommands with completions, and keymap guidance. The code snippets in the document use the Neovim 0.10.0 API.

How do I add type checking to a Neovim plugin?

The guide says to use LuaCATS annotations along with lua-language-server to catch potential bugs in your CI before your plugin's users do. It names the lua-typecheck-action for CI, lux-cli with lx check and lx lint, emmylua-analyzer-rust, luacheck and lazydev.nvim.

Why should Neovim plugin commands use subcommands?

The guide says to gather subcommands under scoped commands and implement completions for each subcommand, rather than polluting the command namespace with a command for each action, because that quickly becomes overwhelming when users rely on command completion. It also points at the mega.cmdparse library as a way to avoid much of the boilerplate.

What keymap rules does nvim-best-practices give?

Two DON'Ts. Do not create excessive keymaps automatically, since that can conflict with user mappings, and the note suggests buffer-local mappings for specific file types, floating windows or Plug mappings as uncontroversial cases. Do not define a fancy DSL for enabling keymaps through a setup function.

What licence is nvim-best-practices released under?

CC0-1.0, a public domain dedication, so the rules can be reused without an attribution requirement. The LICENSE file is at the root of the repository next to the README and the .github directory.

Official sources

  1. Issues
  2. License: CC0-1.0
  3. lumen-oss/nvim-best-practices on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lumen-oss-nvim-best-practices.svg)](https://hysenlabs.com/projects/lumen-oss-nvim-best-practices)

Was this useful?