Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cachix/devenv/llms.txt

Use this file to discover all available pages before exploring further.

Most projects accumulate a collection of shell scripts over time — build helpers, migration runners, deployment utilities. devenv lets you define these scripts directly in devenv.nix so they are versioned with your project, available to every developer who enters the shell, and have guaranteed access to all the packages and environment variables your project declares.

Defining a Script

Use scripts.<name>.exec to define a named script. The script is exposed as an executable in your shell as soon as you run devenv shell:
devenv.nix
{ pkgs, ... }:

{
  packages = [ pkgs.curl pkgs.jq ];

  scripts.silly-example.exec = ''
    curl "https://httpbin.org/get?$1" | jq '.args'
  '';
}
Because scripts are activated alongside the environment, any packages in packages are available by name:
$ devenv shell
Building shell ...
Entering shell ...

(devenv) $ silly-example foo=1
{
  "foo": "1"
}

Passing Arguments

Scripts receive command-line arguments normally. Use $1, $2, etc. for positional arguments, or $@ to forward all arguments:
devenv.nix
{
  scripts.foo.exec = ''
    npx @foo/cli "$@";
  '';
}

Runtime Packages

Sometimes you need a package only when a specific script runs, without adding it to the global environment. Use the packages attribute on the script itself:
devenv.nix
{ pkgs, ... }:

{
  scripts.analyze-json = {
    exec = ''
      # Both curl and jq are available when this script runs
      curl "https://httpbin.org/get?$1" | jq '.args'
    '';
    packages = [ pkgs.curl pkgs.jq ];
    description = "Fetch and analyze JSON";
  };
}
The packages attribute ensures these tools are in the script’s PATH without polluting the global development environment.

Pinning Packages Inside Scripts

For maximum reproducibility, interpolate package store paths directly into the script. This pins the exact binary used, regardless of what is on PATH:
devenv.nix
{ pkgs, ... }:

{
  scripts.silly-example.exec = ''
    ${pkgs.curl}/bin/curl "https://httpbin.org/get?$1" | ${pkgs.jq}/bin/jq '.args'
  '';
}
When a Nix package is interpolated in a string, Nix substitutes the absolute path to that package in the Nix store.
$ devenv shell
Building shell ...
Entering shell ...

(devenv) $ silly-example foo=1
{
  "foo": "1"
}

Scripts in Other Languages

The package attribute lets you run a script using any interpreter. Optionally set binary if the interpreter binary name differs from the package name:
devenv.nix
{ pkgs, config, lib, ... }:

{
  scripts.python-hello = {
    exec = ''
      print("Hello, world!")
    '';
    package = config.languages.python.package;
    description = "hello world in Python";
  };

  scripts.nushell-greet = {
    exec = ''
      def greet [name] {
        ["hello" $name]
      }
      greet "world"
    '';
    package = pkgs.nushell;
    binary = "nu";
    description = "Greet in Nu Shell";
  };
}

Loading Scripts from External Files

The exec attribute also accepts a path, which lets you keep longer scripts in separate files:
devenv.nix
{
  scripts.file-example = {
    exec = ./file-script.sh;
    description = "Script loaded from external file";
  };
}

Listing Scripts in enterShell

The description field on each script combines well with enterShell to print a help menu when developers enter the project shell:
devenv.nix
{ pkgs, config, lib, ... }:

{
  scripts.python-hello = {
    exec = ''
      print("Hello, world!")
    '';
    package = config.languages.python.package;
    description = "hello world in Python";
  };

  scripts.nushell-greet = {
    exec = ''
      def greet [name] {
        ["hello" $name]
      }
      greet "world"
    '';
    package = pkgs.nushell;
    binary = "nu";
    description = "Greet in Nu Shell";
  };

  scripts.file-example = {
    exec = ./file-script.sh;
    description = "Script loaded from external file";
  };

  enterShell = ''
    echo
    echo 🦾 Helper scripts you can run to make your development richer:
    echo 🦾
    ${pkgs.gnused}/bin/sed -e 's| |••|g' -e 's|=| |' <<EOF | ${pkgs.util-linuxMinimal}/bin/column -t | ${pkgs.gnused}/bin/sed -e 's|^|🦾 |' -e 's|••| |g'
    ${lib.generators.toKeyValue {} (lib.mapAttrs (name: value: value.description) config.scripts)}
    EOF
    echo
  '';
}
When you enter the shell, the script list is printed automatically:
$ devenv shell
Building shell ...
Entering shell ...

🦾 Helper scripts you can run to make your development richer:
🦾
🦾 python-hello     Hello world in Python
🦾 nushell-greet    Greet in Nu Shell
🦾 file-example     Script loaded from external file

(devenv) $
For operations that need to run when entering the shell (such as database migrations or code generation), consider using tasks with the before attribute instead of enterShell. Tasks provide better control over execution order and dependencies.

Scripts Option Reference

AttributeTypeDescription
execstring or pathThe script body, or a path to an external script file
descriptionstringHuman-readable description shown in help menus
packageslist of packagesPackages added to PATH only when this script runs
packagepackageInterpreter package (for non-shell scripts)
binarystringBinary name within package (defaults to package name)

Build docs developers (and LLMs) love