Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

TLDR: tix.toml maps file paths to contexts (like @nixos or @home-manager), controlling which stubs get loaded and how module parameters are typed.

tix.toml

Tix auto-discovers tix.toml by walking up from the file being checked. You can also pass --config path/to/tix.toml explicitly.

Contexts

A context tells tix “files matching these paths are NixOS modules (or Home Manager modules, etc.)” so it knows how to type the standard { config, lib, pkgs, ... }: parameter pattern.

[context.nixos]
includes = ["modules/*.nix", "hosts/**/*.nix"]
stubs = ["@nixos"]

[context.home-manager]
includes = ["home/*.nix"]
stubs = ["@home-manager"]
  • includes — glob patterns matching files in this context
  • excludes — glob patterns for files to exclude even when includes matches. Useful when a broad glob like dir/**/*.nix covers a directory with a few files that belong to a different context.
  • stubs — which stub sets to load. @nixos and @home-manager are built-in references to the generated NixOS/Home Manager stubs (requires [stubs.generate] or the TIX_BUILTIN_STUBS env var)

For example, if most files under common/ are NixOS modules but common/homemanager/ contains Home Manager modules:

[context.nixos]
includes = ["common/**/*.nix", "hosts/**/*.nix"]
excludes = ["common/homemanager/**/*.nix"]
stubs = ["@nixos"]

[context.home-manager]
includes = ["common/homemanager/**/*.nix"]
stubs = ["@home-manager"]

tix init generates excludes patterns automatically when it detects mixed-kind directories.

What contexts do

When a file matches a context, tix automatically types the module’s function parameters. A NixOS module like:

{ config, lib, pkgs, ... }:
{
  services.foo.enable = true;
}

Gets config, lib, and pkgs typed according to the context’s stubs, without any doc comment annotations in the file.

callPackage / dependency-injected files

For files loaded via callPackage or import that take a package set as their parameter:

[context.callpackage]
includes = ["pkgs/**/*.nix"]
stubs = ["@callpackage"]

@callpackage derives its types from the built-in Pkgs module (the same one that types pkgs.stdenv.mkDerivation, pkgs.fetchurl, etc.). Parameters not covered by the built-in stubs remain untyped. For broader coverage, generate pkgs stubs and load them via --stubs or the stubs config key — they merge into the Pkgs type alias automatically.

Custom context stubs

For module systems other than NixOS/Home Manager (e.g. flake-parts, devenv), point stubs at a local .tix file:

[context.flake-parts]
includes = ["modules/**/*.nix"]
stubs = ["./flake-parts.tix"]

The file can contain top-level val declarations and/or module blocks; both contribute to context args. See Custom context stubs from a file for the full pattern.

Inline context annotation

You can also set context per-file with a doc comment at the top:

/** context: nixos */
{ config, lib, pkgs, ... }:
{
  # ...
}

Project settings

The [project] section configures project-level behavior for both the LSP and tix check.

[project]
includes = ["lib/*.nix", "pkgs/**/*.nix"]
excludes = ["result", ".direnv", "vendor/**"]
  • includes — glob patterns for files to include in analysis. When the LSP starts, these files are analyzed in the background and their inferred types become ephemeral stubs available to all open files.
  • excludes — glob patterns for files/directories to skip during tix check. Excluded files are fully skipped from discovery. Hardcoded ignores (.git, node_modules, result, .direnv, target) are always applied.

tix init generates a [project] section with sensible defaults.

Files matching includes are processed in the background after LSP initialization. As each file’s type is inferred, any open files that import it are automatically re-analyzed with the updated type information.

Suppression directives

Tix supports TypeScript-style comment directives for suppressing diagnostics:

# tix-nocheck

Suppresses all diagnostics for the entire file. Place anywhere in the file:

# tix-nocheck
{ config, lib, pkgs, ... }:
{
  # This file will not report any type errors
  services.foo.enable = 42;
}

# tix-ignore

Suppresses diagnostics on the next line only:

let
  # tix-ignore
  x = (1 + 2).foo;  # no error reported for this line
  y = (3 + 4).bar;  # this line still reports errors
in
  x

Diagnostics

Control the severity of optional diagnostics. Currently the only configurable diagnostic is unknown_type (E014), which fires when a binding has type ?.

[diagnostics]
unknown_type = "hint"  # "off", "hint", "warning", or "error" (default: "hint")

The LSP editor settings (tix.diagnostics.unknownType) take precedence over tix.toml when both are set.

Runtime stub generation

Tix can generate full NixOS, Home Manager, and pkgs stubs at runtime on first use. The result is cached in the Nix store and reused on subsequent runs.

[stubs.generate]
nixpkgs = "/nix/store/...-nixpkgs-src"
home-manager = "/nix/store/...-home-manager-src"

Each source can be a direct store path or a Nix expression:

[stubs.generate]
nixpkgs = { expr = "(builtins.getFlake (toString ./.)).inputs.nixpkgs" }
home-manager = { expr = "(builtins.getFlake (toString ./.)).inputs.home-manager" }
  • nixpkgs (required) — path to nixpkgs source, or { expr = "..." } to evaluate
  • home-manager (optional) — path to home-manager source; omit to skip HM stubs
  • nixos-modules (optional) — extra NixOS modules appended to the option-tree eval. Their options.* declarations show up in the generated nixos.tix, so config.myproj.databaseUrl is typed, not just upstream NixOS options.
  • home-manager-modules (optional) — same, for Home Manager.
[stubs.generate]
nixpkgs = { expr = "(builtins.getFlake (toString ./.)).inputs.nixpkgs" }
nixos-modules = [
  "./modules/myproj.nix",
  { expr = "(builtins.getFlake (toString ./.)).nixosModules.default" },
]

Entries can be plain path strings (relative to tix.toml) or { expr = "..." } for modules sourced from a flake.

Debugging the extracted schema: the equivalent manual CLI invocation is tix stubs generate nixos --flake . --hostname <NAME> — this evaluates a live NixOS configuration (including your user modules) and emits the stub to stdout or -o <path>, so you can inspect exactly what the schema extractor sees before letting the LSP drive it. Home Manager has an equivalent tix stubs generate home-manager --flake . --username <NAME>.

Module edits are not auto-detected. After changing any file listed under nixos-modules / home-manager-modules, run tix stubs refresh and restart the LSP.

Custom module systems

For arbitrary module systems (flake-parts, devenv, nix-darwin, custom evalModules setups), add a [stubs.generate.systems.<name>] table:

[stubs.generate.systems.flake-parts]
options_expr = "(inputs.flake-parts.lib.evalFlakeModule {...} {...}).options"
context_args = ["config", "lib", "pkgs"]

Each custom system generates a <name>.tix file that you reference in contexts as @<name>. Types are assigned by name: config<Name>Config, libLib, pkgsPkgs, any other name → { ... }. For precise types on extra args (e.g. flake-parts withSystem), layer a hand-written .tix after @<name> in the context’s stubs array — see Typing extra module args precisely. Start by prototyping the options_expr manually with tix stubs generate module — see Custom module systems for the full CLI-first iteration workflow.

On first run, tix invokes nix build to generate .tix stubs from the NixOS option tree, Home Manager options, and nixpkgs package set. This takes 30-60 seconds. Subsequent runs are instant thanks to a lightweight file cache (~/.cache/tix/store-stubs/). Changing either nixpkgs or tix version triggers regeneration.

[stubs.generate] can coexist with manual stub paths:

[stubs]
paths = ["./my-extra-stubs/"]

[stubs.generate]
nixpkgs = { expr = "(builtins.getFlake (toString ./.)).inputs.nixpkgs" }

Resolution priority:

  1. TIX_BUILTIN_STUBS env var (always wins)
  2. [stubs.generate] runtime generation
  3. Compiled-in minimal stubs

Requirements: The tix binary must be installed via Nix (running from /nix/store/...). In dev mode (cargo build), use TIX_BUILTIN_STUBS or nix build .#stubs instead.

Generating tix.toml

Run tix init to automatically generate a tix.toml for your project:

tix init              # Generate tix.toml in current project
tix init --dry-run    # Preview without writing
tix init --yes        # Overwrite existing tix.toml
tix init /path/to/project  # Specify project directory

The command scans all .nix files, classifies each by its structural signals (parameter names, body references, attrset keys), and generates context sections mapping file paths to the appropriate stubs. For flake projects, it also auto-detects nixpkgs and home-manager inputs from flake.lock and generates the [stubs.generate] section.

No-module escape hatch

If tix incorrectly treats a file as a module, add this comment to disable module-aware features:

/** no-module */