Protobuf LSP Setup Guide: VS Code & Neovim with Buf
The editing experience for writing .proto files has long been stuck in the 'stone age' of syntax highlighting and running protoc after saving to see errors: completion by guessing, navigation via grep, and only discovering misspelled field names after compilation. This situation has ended—Buf has packaged a complete LSP server directly into the buf CLI (command buf lsp serve), offering jump to definition, completions, find references, rename, semantic highlighting, and real-time diagnostics aligned with buf lint, all in one setup.
To provide background: Buf's release blog was written in January 2026 and recently resurfaced on Hacker News (163 points, 115 comments). So it's not 'news', but something many people haven't adopted yet—'something that should have been configured six months ago'. LSP is generally available, the wire protocol is stable, and all commands in this article are tested with the latest buf v1.72.0.
Why Choose the Official LSP Over Community Solutions
Protobuf community LSPs have existed, but each has shortcomings:
| Solution | Status |
|---|---|
| Buf LSP (Official) | Packaged in buf CLI, driven by Buf's full-spec compiler frontend protocompile (used internally by Google in some scenarios); updated monthly with the CLI |
| protols | Rust + tree-sitter community solution, active, but parsing is not full-spec |
| protobuf-language-server | Old Go community solution, README admits 'no complete validation' |
| pbls | Personal project, very niche |
| vscode-proto3 | Not LSP, just syntax highlighting + calling protoc, essentially discontinued after 2026-03 |
| buf-language-server | Buf's own old attempt, archived—features merged into buf CLI |
The core advantage of the official solution is not the feature list, but consistency: LSP diagnostics follow the lint rules configured in your buf.yaml, formatting is consistent with buf format output, and jumps understand BSR module dependencies. What you see in the editor is the same conclusion as what runs in CI.
A notable detail under the hood: Buf has developed a new query-driven compiler frontend for LSP (inspired by rustc's query-based architecture), supporting incremental compilation, with higher diagnostic accuracy than protoc. For example, with repeated repeated M x = 4;, it can precisely indicate that the second repeated is redundant and provide a fix suggestion, which protoc cannot do.
Step 1: Install Buf CLI (≥ 1.72.0)
# macOS / Linux (official tap, not homebrew-core)
brew install bufbuild/buf/buf
# Windows
scoop install buf # or: winget install bufbuild.buf
# per-project npm install
npm install @bufbuild/buf # then invoke via npx buf
# download the binary directly
curl -sSL "https://github.com/bufbuild/buf/releases/download/v1.72.0/buf-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/buf && chmod +x /usr/local/bin/buf
After installation, confirm: buf --version outputs 1.72.0 (or newer). The LSP server doesn't require separate installation; buf lsp serve --help showing help indicates it's ready.
A legacy pitfall: earlier materials used the command
buf beta lsp, which is deprecated. The GA command isbuf lsp serve, which defaults to stdio.
VS Code: Just Install the Extension
- Install the extension
bufbuild.vscode-buf(search "Buf" in Marketplace, requires VS Code 1.95+) - Disable other Protobuf extensions first (such as vscode-proto3)—official documentation recommends this to avoid conflicts in highlighting and diagnostics
- That's it. The extension defaults to using the buf in your
$PATH; if not found, it will automatically download the latest CLI to its storage directory. To specify a binary path, use the settingbuf.commandLine.path
Bonus: The command palette directly includes buf generate, buf build, dependency updates, and other commands.
Neovim: Two Methods
Neovim 0.11+ native method (recommended by official documentation):
vim.lsp.config('buf_ls', {
cmd = { 'buf', 'lsp', 'serve' },
filetypes = { 'proto', 'buf-config' },
root_markers = { 'buf.yaml', '.git' },
})
vim.lsp.enable('buf_ls')
nvim-lspconfig old method (equivalent replacement):
require('lspconfig').buf_ls.setup({})
There is an additional step that must be done, which is mentioned in the official documentation but often overlooked: the buf-config filetype needs to be manually registered, otherwise LSP will not attach to configuration files like buf.yaml:
vim.filetype.add({ filename = {
['buf.yaml'] = 'buf-config',
['buf.gen.yaml'] = 'buf-config',
['buf.policy.yaml'] = 'buf-config',
['buf.lock'] = 'buf-config',
}})
Also, uninstall deprecated old plugins: uarun/vim-protobuf, bufbuild/vim-buf, and buf-lint/buf-format in ALE (their responsibilities are now covered by LSP).
JetBrains / Other Editors
- IntelliJ family: Official plugin "Buf for Protocol Buffers" (plugin market ID 19147), also driven by Buf LSP server
- Zed: Install the Proto extension, and in settings.json, set
"language_servers": ["buf"] - Emacs: No official plugin, but lsp-mode / eglot can directly connect to
buf lsp serve
Two-Minute Verification: A Minimal Project
Create a two-file project to verify that jumps and diagnostics are working:
demo/
├── buf.yaml
└── proto/acme/v1/
├── user.proto
└── order.proto
# buf.yaml
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
// proto/acme/v1/user.proto
syntax = "proto3";
package acme.v1;
message User {
string id = 1;
string name = 2;
}
// proto/acme/v1/order.proto
syntax = "proto3";
package acme.v1;
import "acme/v1/user.proto";
message Order {
string order_id = 1;
acme.v1.User buyer = 2;
int64 amount = 2; // deliberate mistake: duplicate field number
}
After configuring LSP, you should immediately see:
- Real-time diagnostics: A wavy line appears at
amount = 2, reportingfield number "2" used more than once—no need to save or compile. It disappears when changed to= 3 - Jump to definition: Place the cursor on
acme.v1.Userand press the jump key, landing on the message definition in user.proto - Completions: Type
acme.to get symbol completions; typeimport "to get path completions - Find references: Reverse lookup on
Userlists usage in order.proto - Rename / Organize imports / File-wide formatting (formatting results are consistent with
buf format)
Command-line cross-verification: buf lint should output the same field number error—this means the wavy lines in the editor and the errors in CI are now the same set of results.
Known Pitfalls
- Keep buf CLI updated. There was an issue with nvim 0.12 + new lspconfig triggering LSP crashes (fixed), but the monthly CLI updates fix bugs quickly—don't lock to old versions
- Formatting is file-wide only, not supporting selected snippet formatting (a known limitation of vscode-buf)
- On Windows, pulling public BSR modules had a 401 authentication issue (fixed); if projects depending on BSR deps fail to pull, upgrade the CLI first
- The Buf extension in VS Code once interfered with formatting by other YAML plugins; if YAML files other than buf.yaml behave abnormally, check for extension conflicts
Frequently Asked Questions (FAQ)
Does Protobuf LSP need to be installed separately?
No. The LSP server is packaged in the buf CLI binary; once buf is installed (brew install bufbuild/buf/buf), it's available, and editors start it via buf lsp serve. The VS Code Buf extension even downloads automatically if buf is not found.
What if buf lsp and protoc errors are inconsistent?
Use buf as the standard and unify the toolchain. Buf's compiler frontend protocompile is a full-spec implementation with more precise diagnostics; if CI still uses protoc while the editor uses buf LSP, consider migrating lint/build to buf as well, so the editor and CI are aligned.
Why does buf.yaml have no LSP features in Neovim?
It's likely that filetype registration was missed. buf-config is not a built-in filetype in Neovim; you need to use vim.filetype.add to map buf.yaml / buf.gen.yaml / buf.lock, so that LSP attaches.
If already using vscode-proto3 or community LSP, is it necessary to switch?
Yes. vscode-proto3 only provides highlighting and post-save diagnostics, with no jump/rename/reference finding, and is essentially discontinued; community LSP parsers are not full-spec. Official LSP diagnostics are aligned with buf lint rules, which is the only solution for 'what you see in the editor is what you get in CI'.