Adding TypeScript to My Neovim Configuration
As my Neovim setup grew more complex, I turned to TypeScript to solve limitations in schema validation and type safety that Lua could not cover on its own. By shifting critical parts of the configuration to TypeScript—such as schema definitions, validation, and scripting—I was able to catch more errors early and keep the environment easier to maintain.
This balance allows Lua to handle the editor’s core behavior while TypeScript ensures structure, safety, and automation around it. Together, they create a workflow that scales far better than Lua alone.
Why Add TypeScript?
- Schema definition and validation
Many parts of my configuration (such as keybindings and dashboard shortcuts) are defined in JSON. TypeScript allows me to create schemas that can be compiled into JSON for validation, ensuring consistency and reducing errors. - Improved type safety
While LuaDoc offers some basic annotation support, it does not match the type guarantees of TypeScript. Adding TypeScript ensures that the tooling around my configuration is backed by a stronger type system. - Tooling and automation
With Node.js scripts (written in.mjs
) and TypeScript-driven schema definitions, I can automate configuration tasks such as schema builds and snippet synchronization. - Consistency with project languages
Since I frequently work with TypeScript in other contexts (applications, serverless functions, and libraries), it was natural to integrate it into my Neovim setup for consistency.
Language Packs
Language support in my configuration is unified through a language pack system. Each pack defines all the tools and settings required for a given language, consolidating them into a single source of truth. Instead of maintaining separate plugin configurations for Treesitter, LSP, linters, and formatters, I simply define them under one key in a list.
A language pack can declare:
- Mason packages – binaries and tools managed via mason.nvim .
- LSP servers – language servers configured under
./lsp/
. - Treesitter parsers – built-in or custom parser definitions for syntax highlighting.
- Linters and formatters – registered through
nvim-lint
andconform.nvim
.
Enabling a language is as simple as defining its key in the configuration. For example, adding support for PHP, Haskell, or C# requires only toggling their entry in the list, and the system ensures:
- The correct Treesitter parser is installed.
- The appropriate LSP server is configured.
- Required linters and formatters are registered.
- Any supporting tools are installed via Mason.
This approach makes my configuration highly scalable: whether I need TypeScript, PHP, Haskell, or C#, I can enable or disable complete language support with a single change, while keeping the environment consistent across all languages.
Schema-Driven Keymaps
In addition to language packs, my configuration uses a schema-driven approach for keymaps. Keybindings are defined in TypeScript, validated through schemas, and compiled into JSON. Neovim then consumes the JSON, applying the mappings in a structured and predictable way.
This method provides two major benefits:
- Type safety at definition time – invalid or inconsistent keybindings are caught early.
- Consistency across features – keymaps, like language packs, are centralized and declarative.
By combining schemas with TypeScript’s type system, I ensure that even something as dynamic as keybindings remains reliable and maintainable.
Plugin Management with Lazy.nvim
Plugins in my configuration are loaded using Lazy.nvim , which provides a fast, declarative approach to plugin management. Instead of toggling plugins directly in Lua files, I use an enabled.ts
file to control which modules should be active.
This file references a JSON document listing all available modules. TypeScript enforces type safety, ensuring that only valid modules are referenced. If a module does not exist in the JSON schema, the TypeScript compiler immediately flags the issue, preventing invalid configuration states.
This system has three key advantages:
- Type safety for plugin toggles – prevents misconfigured or misspelled plugin references.
- Single source of truth – the JSON file defines the authoritative list of modules.
- Declarative configuration – enabling or disabling a plugin becomes as simple as flipping a boolean in
enabled.ts
.
By combining Lazy.nvim’s plugin manager with TypeScript validation, I can keep my configuration modular, predictable, and less error-prone.
Conclusion
Adding TypeScript to my Neovim configuration was not about expanding the editor into a new role—it already serves as my primary IDE. The motivation was to bring stronger type safety, structured schema management, and improved tooling into the ecosystem.
By combining Lua for the core editor configuration with TypeScript for schemas, validation, and automation, I gain the strengths of both languages. With language packs, schema-driven keymaps, and plugin management through Lazy.nvim and TypeScript, my environment remains robust, consistent, and adaptable.
Links
- https://github.com/stephansama/nvim?tab=readme-ov-file#stephansama-nvim
- https://github.com/stephansama/nvim/pull/9
- https://typescripttolua.github.io/