Why ZLS Exists (Because Zig Without It Sucks)

ZLS in Action

Writing Zig without autocomplete is fucking miserable, so that's why ZLS exists. I spent three months debugging phantom errors before I realized half were typos that any decent language server would've caught.

ZLS 0.15.0 dropped August 24, 2025, and only works with Zig 0.15.1 - not 0.15.0 because that was retracted for being broken. Version compatibility is stricter than a bank loan officer. The smart thing about ZLS is it uses LSP, so they only have to build one thing that works with every editor instead of writing VS Code extensions, Vim plugins, and Emacs modes separately.

How This Thing Actually Works

Your editor talks to ZLS through JSON-RPC messages - you type code, editor sends it to ZLS, ZLS analyzes it and sends back completions and errors. When ZLS gets better, every editor wins. Basic stuff.

The hard part is ZLS has to understand Zig's batshit crazy features like comptime where code runs at compile time. Spoiler alert: comptime analysis barely works. Try anything beyond const foo = @import(\"std\") and ZLS gives up. Don't expect rust-analyzer levels of intelligence here.

Setup ranges from "click install" (VS Code) to "sacrifice a goat and read three blog posts" (everything else).

Version Hell Will Ruin Your Day

This bears repeating: version mismatches will fuck your entire week. ZLS 0.15.0 works with Zig 0.15.1. Period. Mix versions and you get Segmentation fault (core dumped) with zero context about what went wrong.

I've been burned three times by this shit. Used ZLS 0.14 with Zig 0.15 and got segfaults that took two days to debug. The error message? "Unexpected token '}'" on a perfectly valid file. Thanks for nothing.

ZLS auto-detects your `build.zig` which works fine for hello world bullshit. Once you have actual dependencies or custom build steps, get ready for "unable to resolve symbol" errors on code that compiles just fine. At least ZLS 0.15.0 shows build errors inline now instead of making you tab over to the terminal every five minutes like a caveman.

What You Actually Get

Zig GitHub Stats

ZLS has 4.1k stars which is decent for a language server that half the time makes you restart it. The zigtools org actually maintains this thing and ships updates regularly.

The basics work: autocomplete, go-to-definition, error squiggles. That's it. Comptime analysis is a joke - try any real metaprogramming and it gives up. Refactoring support makes rust-analyzer look like magic by comparison. But honestly? For normal Zig code it's the difference between "tolerable" and "wanting to switch back to C++."

Even TigerBeetle and Bun use it, so it can't be completely worthless.

ZLS vs Other Language Servers (AKA Why Rust Devs Are Spoiled)

What Actually Matters

ZLS (Zig)

rust-analyzer (Rust)

clangd (C/C++)

gopls (Go)

Getting it installed

Download and run (unless you hate yourself and use Vim)

rustup handles it, zero bullshit

CMake hell or LLVM pain

Just fucking works

Actually understanding your code

Basic stuff works, comptime makes it cry

So good it spoils you for life

Decent if your includes aren't fucked

Rock solid

When shit breaks

Shows compiler errors live

Amazing borrow checker help

Good but template errors are cryptic

Clear and actually helpful

Refactoring

Can rename variables. That's it.

Refactors better than you can manually

Barely exists

Pretty decent

Playing with build systems

Finds build.zig automatically

Cargo just works (shocking)

Good luck with CMakeLists.txt

Go modules are actually beautiful

Memory usage

Light (~30MB)

Memory hungry beast (~150MB)

Reasonable (~80MB)

Light (~40MB)

When the language changes

Breaks every 6 months with Zig updates

Rock solid

Ancient and stable

Stable as fuck

What ZLS Actually Does (And Where It Breaks)

Zero the Ziguana - Zig Mascot

Zig Logo

LSP Features That Sometimes Work

Zig VS Code IntelliSense

ZLS 0.15.0 claims to support the usual LSP features. Here's what you'll actually get:

Shit that works most of the time:

  • Autocomplete - Standard library stuff works, your own functions... maybe
  • Go-to-definition - Works unless you have comptime, then good fucking luck
  • Error squiggles - Real-time compiler errors (the one thing that actually works reliably)
  • Formatting - zig fmt integration is solid, can't break that
  • Find references - Basic symbol hunting works if ZLS isn't confused

Shit that works when Mercury is in retrograde:

  • Hover info - Sometimes shows types, sometimes shows "unable to resolve"
  • Comptime completions - Ha ha ha no. Maybe for @import("std")
  • Symbol renaming - Can rename local variables. That's it. Don't get greedy.
  • Workspace search - Finds symbols when your build config isn't fucked

What's Actually New in 0.15.0

The August 2025 release finally fixed some annoying shit:

Build Errors Show Up Inline:
ZLS now shows build.zig errors right in the editor instead of making you tab over to the terminal like a caveman. When your build script is borked, you'll know immediately instead of spending 10 minutes wondering why nothing compiles.

Unused Variables Don't Look Like Errors:
Unused vars get dimmed instead of showing red squiggles. Small win but I'll take it.

Syntax Highlighting Actually Makes Sense:
`var` vs `const` highlighting plus control flow highlighting that shows matching break/continue. Useful when you have nested loops that make your brain hurt.

Editor Setup War Stories

VS Code (The Only Sane Option):
Install the official extension, restart, done. This is the only setup that doesn't require googling error messages for three hours.

Neovim (For Masochists):
Modern configs usually have ZLS working. If you're building your own nvim config... Christ, just use the VS Code extension until your project is done.

Everything Else (Abandon Hope):
Installation guides exist but good luck. Emacs users: expect to edit init files. Sublime Text: hope you like JSON config files. JetBrains: their plugin system is a maze.

Nuclear option: ZLS stops working? Kill it and restart. Works 80% of the time. I don't know why this fixes most problems, but it does. Don't spend two hours debugging like I did - just restart the damn thing and move on with your life.

Build System Integration (AKA Dependency Hell)

ZLS auto-detects `build.zig` which works great until you have actual dependencies. Simple projects? Fine. The moment you add packages or custom build steps, ZLS gets confused and starts showing "unable to resolve" errors on perfectly valid code.

Real example: I had a project with a custom package path. ZLS couldn't find any symbols from that package. Spent four hours debugging, reading ZLS source code, checking my build.zig fifty times. Turns out ZLS doesn't handle relative paths in build.zig properly. The fix? Hardcode the absolute path. Fucking beautiful design right there.

Cross-compilation support exists but it's flaky. ZLS tries to match completions to your target but sometimes shows x86_64 symbols when you're targeting ARM. Close enough, I guess.

WebAssembly Support (Because Why Not)

WebAssembly Logo

ZLS compiles to WASM now because someone thought "let's make the slow language server even slower." The Zig Playground uses this for browser development. It works but holy shit is it slow.

Good for quick experiments when you can't install local tools. Bad for anything that requires performance. Like thinking.

Hard-learned lesson: Every time Zig releases a major version (every 6 months), ZLS breaks completely. Not "some features don't work" breaks - "segfaults on startup and ruins your entire day" breaks. I learned this the hard way during a deadline crunch. Budget a full day to upgrade and debug whatever compatibility nightmare they've cooked up this time.

Frequently Asked Questions About ZLS

Q

How do I install ZLS for my editor?

A

VS Code: Just fucking install the official extension and it handles everything. For masochists using other editors, download binaries from zigtools.org or compile from source with git clone https://github.com/zigtools/zls && cd zls && zig build -Doptimize=ReleaseSafe. Then fight with editor configs for an hour using the setup guides.

Q

Which Zig version should I use with ZLS?

A

Version compatibility is anal-retentive strict: ZLS 0.15.0 only works with Zig 0.15.1 (not 0.15.0 because that was broken and retracted). Mix versions and get Segmentation fault (core dumped) with zero context. I've debugged this exact error three times because the error message tells you nothing useful. The install tool tries to match versions but double-check or you're fucked.

Q

Why doesn't ZLS understand my comptime code?

A

Because comptime analysis is a half-implemented mess. Basic stuff like const foo = @import("std") works. Try any real metaprogramming and ZLS throws up its hands with "unable to resolve" errors. I've got comptime code that generates 50 functions

  • ZLS sees exactly zero of them. Don't expect rust-analyzer-level intelligence, expect "maybe it'll work if I restart it."
Q

How do I configure ZLS for my project?

A

Supposedly you need zero config

  • ZLS finds build.zig automatically.

Does it work? Sometimes. When it doesn't, create zls.json in your project root and pray. The config guide exists but the format is fucking annoying so most people just use defaults and curse when it breaks.

Q

Does ZLS work with cross-compilation projects?

A

Sometimes. ZLS claims to support Zig's cross-compilation but it gets confused easily. Your build.zig has multiple targets? ZLS picks one (usually the wrong one) and shows completions for that. I've had ZLS show Windows-specific symbols when targeting Linux. Close enough for government work, I guess.

Q

Why is ZLS eating all my CPU?

A

Probably infinite comptime loops making your CPU scream. ZLS 0.15.0 supposedly improved file watching but it still chokes on large projects. If your laptop fan sounds like it's taking off, check for comptime recursion that never terminates. Or just restart ZLS. That fixes it 60% of the time.

Q

Can I use ZLS with C/C++ code in my Zig project?

A

ZLS only gives a shit about Zig code. It understands @cImport and C library integration through your build system but won't help with C/C++ source files. Mixed projects? Use ZLS + clangd together and configure your editor to pick the right language server per file. More config files to manage, yay.

Q

How stable is ZLS for production development?

A

The basics work fine, everything else breaks every 6 months. Autocomplete, go-to-definition, formatting won't crash. But Zig is pre-1.0, so every major release (twice a year) breaks ZLS compatibility. Pin your ZLS version in CI or watch your builds explode. Budget a full day every 6 months to upgrade and debug whatever the fuck broke this time.

Q

Does ZLS support debugging integration?

A

No, ZLS is just a language server, not a debugger. For debugging use GDB/LLDB with zig build -Doptimize=Debug. Some editors bolt debugging onto the LSP features but that's separate from ZLS itself. ZLS analyzes code, debuggers run code, different jobs.

Q

Why doesn't ZLS work in my browser/online IDE?

A

Because WASM ZLS is slow as molasses. ZLS 0.15.0 has WASM builds for browser usage but it's painfully slow compared to native. The Zig Playground uses it for demos but you wouldn't want to do real development this way. Good for quick experiments, shit for actual work.

Q

How do I report ZLS bugs or request features?

A

**GitHub issues

  • include ZLS version, Zig version, editor, and steps to reproduce**.

The maintainers actually respond unlike some OSS projects. For real-time help/complaining, join the ZLS Discord where you can get answers from people who've hit the same problems.

Q

Can ZLS analyze Zig code without a build.zig file?

A

Yeah, single files work fine for basic stuff

  • syntax highlighting, stdlib completions, error checking. But don't expect cross-file analysis or dependency resolution without proper project structure. Good for learning Zig, not for real projects with multiple files and dependencies.
Q

How does ZLS handle large codebases?

A

It struggles but doesn't die completely. ZLS indexes code incrementally and caches results, but performance tanks on huge projects or comptime-heavy code. For massive codebases, exclude unnecessary directories or ZLS will spend forever analyzing files you don't care about. Memory usage climbs steadily with project size.

Q

Does ZLS support custom Zig packages?

A

Sometimes, when it's not confused. ZLS reads your build.zig and tries to provide completions for packages. Works great for simple setups. Complex package configurations or relative paths? Good luck. If completions stop working, restart ZLS

  • it gets confused about package paths easily.
Q

Why does ZLS keep crashing or restarting?

A

Version mismatches, infinite comptime loops, or corrupted cache. Segmentation fault usually means Zig/ZLS version mismatch. "Language server crashed"? Probably an infinite comptime loop eating all memory. Try deleting the .zls directory to clear the cache, or check ZLS logs to see what actually broke.

Q

ZLS says "unable to resolve" for symbols that clearly exist. What gives?

A

ZLS is having an existential crisis about your project structure. Complex build.zig, circular imports, or any non-trivial package setup breaks it. Step 1: restart ZLS. Step 2: make sure zig build actually works. Step 3: delete the .zls cache directory and sacrifice a rubber duck. If none of that works, your build config is probably fucked and ZLS is just the messenger.

Essential ZLS Resources (And Which Ones Are Actually Useful)