crystalline: a Crystal language server, and the author warning you about it
A Language Server Protocol implementation for Crystal. 🔮
At a glance
- What is it?
- crystalline is a Language Server Protocol implementation for Crystal that provides go-to, autocompletion and semantic checking by driving the Crystal compiler itself, which is why the semantic analysis works and why a full language server is impossible. The author maintains it passively and says so at length in the readme.
- Who is it for?
- crystalline suits a Crystal developer who wants real semantic checking in their editor and is prepared to deal with the rough edges, since the checking comes from the compiler and is therefore as accurate as the compiler is. It does not suit you if you need the feature set of a full language server, because the compiler's architecture makes that impossible and the author says so.
- Can I use it commercially?
- Yes. MIT 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 35 days ago.
- What is it written in?
- Mainly Crystal, according to GitHub's language statistics.
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 language server for Crystal that the author no longer enjoys maintaining
crystalline is a Language Server Protocol implementation for the Crystal language, written in and for Crystal, and it aims to provide limited language features like go-to, autocompletion, syntax and semantic checking, and to ease development with any compatible code editor.
The project status note at the top of the readme is the most important thing in it, and it is unusually blunt. The author is passively maintaining the project, which means they will not be regularly adding new features or fixing bugs. Their time is very limited and they have other projects they want to work on.
The rest of the note explains the history. Crystalline begun as a fun experiment because at the time there was no working LSP for Crystal, and was written mainly to cater to the author's own needs on their free time. It was open-sourced because the author thought it could be useful to others.
Then the part that makes the note worth reading carefully. The author says that over time some vocal people in the Crystal community had been openly trash-talking the project in the unofficial Discord channel, telling users to avoid it and spreading misinformation, which the author is not cool with. The response is not to stop using it but to argue its value on the grounds that the author is the primary user of it and knows what it can do.
And there is an explicit statement of a limitation that explains the whole design. Due to the nature of the Crystal language and the way the compiler works, it is not possible to provide a full-fledged language server with all the features one would expect. If you have a problem with that, the suggestion is to use another language, or wait for somebody else to write a better LSP, which the author would be happy to see but doubts will happen.
The closing line is that the author is not paid to work on this project, they do it for fun, and unfortunately the fun has been sucked out of it.
So the honest framing of this project is a capable tool under a deliberately low maintenance commitment, with a hard technical ceiling that comes from the compiler rather than from the implementation.
Pre-built binaries first, because building from source takes twenty minutes
The installation advice is blunt about what to do and not do. The recommended method is to download and use the pre-built binaries when possible, and building from source does take a long time. The warning later in the document puts a number on it, several minutes and up to twenty minutes depending on your hardware.
For Linux on x86_64 the download is a wget of a gzipped binary from the latest release, followed by decompressing it and making it executable:
wget https://github.com/elbywan/crystalline/releases/latest/download/crystalline_x86_64-unknown-linux-musl.gz -O crystalline.gz &&\
gzip -d crystalline.gz &&\
chmod u+x crystallineThe musl suffix in the file name is worth noticing, since it means the binary is statically linked against musl rather than glibc, which is what lets the same artefact run on distributions with different C library versions.
For ArchLinux there is a package manager entry, and for MacOS the homebrew route:
brew install crystallineThere are two more routes for people who need something specific. A specific release is available from the releases page, and for a specific commit the binaries are uploaded as artifacts during the CI build, which is the way to get a build of a commit that is not a tag.
The compatibility table is a single row, and it is the kind of table that says the project tracks the language closely. Crystal 1.21 pairs with Crystalline 0.20.
There is one more detail in the build section that explains a design decision in the implementation. On Crystal 1.21 and later the execution-contexts runtime is used by default, and compilations run on a dedicated context, which keeps the server responsive while building. On older versions you add the preview_mt flag to build the legacy multithreaded runtime instead.
That is a direct consequence of the compiler being the analysis engine. A language server that shells out to the compiler has to compile something, compiling takes seconds, and if it happens on the request thread then the editor freezes. Moving the compile to a separate execution context is what keeps the editor responsive, and it is why the runtime choice is tied to the language version rather than being a free choice.
The release history shows the same tight coupling to the language. Version 0.18.0 in May 2026, 0.19.0 in August 2026 and 0.20.0 on the same day as the last push in September 2026.
The build needs llvm, and the two macOS failures are documented
The build from source section comes with two collapsible troubleshooting entries for macOS, and both are specific enough to be worth reading.
The first is about llvm-config. The crystal compiler needs llvm to build crystalline, and if the compiler cannot locate the llvm-config binary the error is a command not found followed by a Crystal backtrace pointing at the LLVM library. The fix is to add the location of the llvm-config binary to the LLVM_CONFIG environment variable, or the containing directory to PATH. The example given is a typical macOS setup where the declaration is prefixed to the command, and the note says to replace env with export on Debian and its derivatives.
The second is about a library not found for a tbd file. LLVM 10.0.1 has some issues when reporting required system libraries on macOS, and the manifestation is that the system libraries list contains a name that the compiler then cannot find, because the expected file name is unlikely to be located during compilation.
The documented workarounds are two. One is to add a symbolic link to the correct shared library file, pointing the expected name at the real one. The other is simply to use a different LLVM major version until the issue is fixed upstream. The first is described as hacky, and the second is the advice that actually matters.
The two-step global install is the plainest route:
# Produces a binary at ./bin/crystalline
shards build crystalline --release --no-debug --progressWait, that is the scoped install. The global one clones the repository, changes into it, installs the shards dependencies, creates a bin directory, and then runs the crystal compiler directly on the source file with the release and no-debug flags.
The scoped install is the alternative, and it goes through the shards package manager. In the shard.yml file you add the project as a development dependency with the GitHub source and the master branch:
development_dependencies:
crystalline:
github: elbywan/crystalline
branch: masterThen the shards build command produces a binary in the bin directory:
shards build crystalline --release --no-debug --progressBoth routes exist because a Crystal project already has a shard.yml, and a language server that has to be built from source is easier to live with if it can be pinned as a dependency rather than built and installed by hand.
The Docker build fetches its own compiler, which explains the long build
The Dockerfile is the clearest single explanation of why building from source takes twenty minutes, and it is worth reading in full.
FROM crystallang/crystal:1.20.2-alpine
WORKDIR /app
# Add llvm deps.
RUN apk add --update --no-cache --force-overwrite \
llvm20-dev llvm20-static g++ libxml2-static zstd-static make
# Build crystalline.
COPY . /app/
RUN git clone -b 1.20.2 --depth=1 https://github.com/crystal-lang/crystal \
&& make -C crystal llvm_ext \
&& CRYSTAL_PATH=crystal/src:lib shards build crystalline \
--no-debug --progress --stats --production --static --release \
-Dpreview_mt --ignore-crystal-version \
&& rm -rf crystal
The base image is the Crystal alpine image at a pinned version. The llvm dependencies are added first, with the alpine package manager, and they include the llvm development and static packages, a C++ compiler, the static xml2 library, the static zstd library and make.
Then the build step clones the Crystal compiler at the matching version with a shallow clone, and runs the make target that builds the llvm extensions. That is the long part, and it is a full compiler build inside a container image.
Only after that does it build crystalline itself, with the Crystal path pointing at the cloned compiler and its own library directory, using shards with the no-debug, progress, stats, production, static and release flags, plus the preview_mt define and a flag to ignore the crystal version. The cloned compiler is removed at the end.
The ignore-crystal-version flag is there because the image pins a specific Crystal version while the project requires a different one, and the build has to proceed anyway.
So the twenty minutes is not the language server being hard to compile. It is the Crystal compiler being built from source inside the image, because the alpine image does not ship a matching prebuilt compiler with the right LLVM extensions.
The static flag in the build is what makes the resulting binary runnable on a musl system without a matching C library, which lines up with the musl suffix on the released Linux binary.
The top level of the repository shows the rest of the shape: a src/ directory for the implementation, a spec/ directory for the tests, an assets/ directory with the icon and the demo animation and the editor screenshot, a shard.yml and shard.lock for the dependency set, a Dockerfile, a .sentry.yml for error reporting, and the usual editor and repository files.
Editor setup is two steps in VSCode and a plugin in Vim
crystalline is meant to be used alongside an editor extension, and the setup instructions are deliberately short.
For VSCode the instructions are to add the Crystal Language extension, type the absolute location of the binary into the corresponding field in the configuration, and then reload the window with the reload window command, or restart VSCode as an alternative.
The absolute path requirement is the detail that catches people. The extension is not searching your PATH for the binary, it is being told exactly where it is, and a relative path or a bare command name will not work.
For Vim and Neovim the route is through Conquer of Completion, which can be configured to use crystalline as its LSP backend and gives the same features as the VSCode setup. The instructions are to download the vim-crystal plugin and then download CoC.
Both routes are thin wrappers over the same protocol, and the reason there is no configuration for the server itself in the readme is that there is nothing to configure. There is no host, no port, no workspace root and no command line arguments, because the server speaks LSP over standard input and output, which is what the protocol specifies.
That is worth contrasting with the server's own responsibilities, which are substantial. The features it provides are go-to-definition, autocompletion, and syntax and semantic checking, and the semantic checking is the hard part, because it comes from the Crystal compiler rather than from a separate analyser.
The readme is also explicit that the author knows the project's reputation problem. The claim that Crystalline has a bunch of issues, but that if you know what you are doing it can be a very useful tool, is a warning rather than a sales pitch, and it is the one you should take at face value when you decide whether the project fits your editor and your workflow.
Editorial conclusion
crystalline suits a Crystal developer who wants real semantic checking in their editor and is prepared to deal with the rough edges, since the checking comes from the compiler and is therefore as accurate as the compiler is. It does not suit you if you need the feature set of a full language server, because the compiler's architecture makes that impossible and the author says so. Before you rely on it, read the project status note rather than skipping it, because the maintenance commitment is explicitly passive and the author lists real limitations, and prefer the pre-built binaries since a source build can take twenty minutes compiling the Crystal compiler itself.
Frequently asked questions
What does crystalline do for Crystal developers?
It is a Language Server Protocol implementation written in and for Crystal, providing go-to, autocompletion, syntax checking and semantic checking, and it works with any editor that speaks the protocol. The readme notes that a full-fledged language server is not possible because of how the Crystal compiler works.
How do I install crystalline?
Download a pre-built binary, since building from source takes up to twenty minutes. On Linux x86_64 you wget the gzipped musl binary from the latest release, decompress it and make it executable. On MacOS use brew install crystalline, on ArchLinux the package manager entry, and a specific commit can be built from the CI artifacts.
Why does crystalline need llvm-config when building from source?
Because the Crystal compiler requires llvm, and the compiler cannot locate the llvm-config binary without help. The fix is to add the location of the binary to the LLVM_CONFIG environment variable, or its containing directory to PATH, prefixing the build command with the env declaration on macOS and using export on Debian and its derivatives.
How do I set up crystalline in VSCode?
Install the Crystal Language extension, type the absolute location of the crystalline binary into the corresponding configuration field, and reload the window. The path has to be absolute because the extension is not searching the PATH for the binary.
Which Crystal version does the current crystalline release support?
The compatibility table pairs Crystal 1.21 with Crystalline 0.20, and the releases are 0.18.0, 0.19.0 and 0.20.0 across 2026. On Crystal 1.21 and later the execution-contexts runtime is used so compilations run on a dedicated context and the server stays responsive while building.
Official sources
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.
[](https://hysenlabs.com/projects/elbywan-crystalline)Was this useful?