When Neovim meets Go
You open Neovim to write a Go function. You type fmt. and wait for the autocomplete menu. Nothing happens. You type a variable name that doesn't exist, and the editor stays silent. You save the file, and the red squiggle that should be there is missing. Neovim is fast, but out of the box it's just a text editor. It doesn't know Go exists. You need to bridge the gap between the raw editor and the Go toolchain. That bridge is gopls, the Go language server.
Most developers coming from VS Code or JetBrains expect their editor to understand code. Neovim gives you the canvas, but you have to provide the intelligence. gopls is that intelligence. It's the official language server from the Go team, and it turns Neovim into a full-featured IDE for Go development.
What gopls actually does
gopls runs in the background and talks to your editor over a protocol called LSP (Language Server Protocol). Think of LSP as a universal translator. Your editor speaks "LSP," and gopls speaks "Go." When you type, Neovim asks gopls, "What completions make sense here?" gopls analyzes your code, checks the types, looks at imports, and replies with a list of suggestions.
It does more than autocomplete. gopls finds definitions, lists references, renames symbols across files, and catches errors before you compile. It also runs formatters and linters. The Go community has standardized on gopls because it's maintained by the same people who write the compiler. It knows the language rules better than any third-party plugin ever could.
Other languages often have fragmented tooling where you pick a linter, a formatter, and a language server from different authors. Go is different. gopls bundles the official analysis tools. You get one source of truth.
Install the language server
Go handles its own tooling, so you don't need a package manager or a plugin manager to install gopls. The go command fetches the source, compiles it, and drops the binary in your tool directory.
Run this in your terminal:
# Fetches the latest gopls binary and places it in GOPATH/bin.
# Run 'go env GOPATH' to find the directory.
go install golang.org/x/tools/gopls@latest
The binary lands in GOPATH/bin. Make sure that directory is in your shell's PATH. If it isn't, Neovim won't find the command. This is how Go developers install CLI tools. No npm install -g, no pip install. Just go install. The tool lives alongside your Go installation and updates independently.
Minimal configuration
Now configure Neovim. If you're using a plugin manager like lazy.nvim or packer, you likely have nvim-lspconfig installed. This plugin provides the boilerplate to start language servers. Add this to your Lua config:
-- Requires nvim-lspconfig. This block tells Neovim to start gopls for Go files.
require('lspconfig').gopls.setup {
-- Enable the server. Empty settings use defaults, which are usually fine.
settings = {},
}
Restart Neovim and open a .go file. You should see gopls appear in the status line or message area. Type code and watch the diagnostics appear. The minimal config works, but it leaves features on the table.
gopls is the brain. Neovim is the hands.
Realistic configuration
The Go community has settled on a richer setup. You want type hints, parameter names, static analysis, and security checks. Here's a configuration that matches what most Go developers use in production.
-- Realistic setup with hints, staticcheck, and vulnerability scanning.
require('lspconfig').gopls.setup {
settings = {
gopls = {
-- Run staticcheck for deeper analysis beyond the compiler.
staticcheck = true,
-- Fill in placeholder arguments when calling functions.
usePlaceholders = true,
hints = {
-- Show variable types on assignment. Helpful for learning Go types.
assignVariableTypes = true,
-- Show constant values inline.
constantValues = true,
-- Show parameter names in calls.
parameterNames = true,
},
},
},
init_options = {
-- Check imports for known vulnerabilities.
vulncheck = "Imports",
},
}
This config enables several powerful features. staticcheck runs a suite of rules that catch common bugs and style issues the compiler misses. It warns about unused parameters, inefficient string concatenation, and deprecated functions. Enabling it in gopls gives you those warnings directly in the editor without running a separate linter process.
usePlaceholders changes how autocomplete works. When you select a function completion, gopls inserts zero-values for all arguments. You get fmt.Sprintf("") instead of fmt.Sprintf(). You can tab through the arguments and fill them in. This saves time when working with functions that have many parameters.
The hints section adds inline information. assignVariableTypes shows the type next to the variable on assignment. constantValues displays the value of constants. parameterNames shows argument names during function calls. These hints make the code self-documenting and reduce the need to hover over symbols.
vulncheck scans your imports against the vulnerability database. It warns you if you import a package with a known CVE. This is good practice for supply chain security. You get warnings in the editor before you commit the code.
Hints make the code self-documenting. Trust the hints.
How the workflow feels
When you open a .go file, Neovim detects the filetype. lspconfig sees the filetype is go and launches gopls. gopls reads your module, indexes the dependencies, and starts listening. The indexing happens in the background. You can start typing immediately.
You type code. Neovim sends a textDocument/didChange notification. gopls updates its internal state and sends back diagnostics if it finds issues. You hover over a function. Neovim sends textDocument/hover. gopls replies with the signature and doc comment. The round-trip is fast enough that it feels instant.
gopls also integrates with gofmt. When you save a file, Neovim can ask gopls to format the code. gopls runs gofmt and returns the formatted text. Neovim applies the changes. You don't need a separate formatter plugin. The Go community uses gofmt for everything. It's mandatory. Let the tool decide indentation and spacing. Argue logic, not formatting.
gopls respects Go conventions. It knows that context.Context should be the first parameter. It knows that error returns should be handled. It suggests idiomatic patterns. If you write code that violates conventions, gopls warns you. This helps you learn Go style as you code.
gopls knows Go better than any plugin. Let it guide you.
Pitfalls and errors
Things go wrong. gopls might not start. You might see gopls: command not found in the Neovim message area. This usually means GOPATH/bin isn't in your shell's PATH, or Neovim isn't inheriting the environment correctly. Check your shell config. Run echo $PATH in Neovim's terminal to verify.
If you mistype the package path in go install, you get a module error. The compiler rejects this with module golang.org/x/tools/gopls: not a known package. Double-check the path. The correct path is golang.org/x/tools/gopls.
gopls needs a go.mod file. If you open a random .go file outside a module, gopls can't resolve imports. You'll see diagnostics like could not import package. Run go mod init to fix this. Go is module-centric. Every project needs a module. gopls uses the module to find dependencies and manage versions.
Version mismatch is another issue. If you update Go but not gopls, you might get subtle bugs. gopls is tightly coupled with the Go version. Run go install golang.org/x/tools/gopls@latest whenever you update Go. This ensures gopls supports the latest language features.
gopls analyzes the whole workspace. If your project is large, indexing takes time. You might see a delay when opening the first file. Subsequent files open instantly. If you're working on a massive codebase, consider using go.work files to manage multiple modules. gopls supports workspaces and can index multiple modules at once.
No module, no magic. Always initialize a module.
When to use gopls
Use gopls with nvim-lspconfig when you want the official, supported language server with zero friction.
Use gopls with staticcheck enabled when you want deeper linting without running a separate linter process.
Use gopls with vulncheck when you care about supply chain security and want warnings on vulnerable imports.
Use a minimal gopls config when you're on a slow machine and need to save RAM; disable hints and staticcheck to reduce overhead.
Use gopls alone when you're writing standard library code or simple scripts; you don't need extra plugins for basic Go development.
gopls is the standard. Stick to it.