Nix Language Basics

Primitive Values

These values are mostly similar to JSON:

integerWhole number1
floatFloating point number1.054
stringUTF-8 string"hello!"
pathFile or url./default.nix

NOTE: Paths are special. They will be resolved relative to the file. The value must contain a "/" to be considered a path, however, it's common to construct the value starting with "." to avoid confusion (e.g. ./foo/bar vs foo/bar). If a path is referenced as part of a package, that path will be added to the nix store, and all references to that path will be substituted with the nix store path.


Nix exposes two ways to express strings. Strings are enclosed with double quotes: "hello". This works well for small strings, such as simple flags. However, it's common to write a block of commands which need to be executed; for this, nix also has multi-line support with the "lines" construct. "lines" are denoted by two single quotes.

Example usage of lines:

  postPatch = ''
    mkdir build
    cd build

Here, we have postPatch being assigned a series of commands to be ran as part of a build.

Another quality of lines, is that all shared leading whitespace will be truncated. This allows for the the lines blocks to be adjusted to the indention of the parent nix expression without influencing the contents of the string.

$ cat lines.nix
  2 spaces
   3 spaces
    4 spaces
$ nix eval -f lines.nix
"2 spaces\n 3 spaces\n  4 spaces\n"


Lists work similarly to most other languages, but are whitespace delimited. [ 1 2 ] is an array with elements 1 and 2.

Note: For oddities around lists and elements which use whitespace, please see list common mistakes.

Attribute Set (Attr set)

This can be thought of as a dictionary or map in most other languages. The important distinct is that the keys are always ordered, so that the order doesn't influence how a derivation will produce a hash. Attr sets values do not need to be of the same type. Attr sets are constructed using an = sign which denotes key value pairs which are separated with semicolons ;, the attr set is enclosed with curly braces { }. Selection of an attribute is done through dot-notation <set>.<key>.

nix-repl> a = { foo = "bar"; count = 5; flags = ''-g -O3''; }
nix-repl> a.count

# Shorthand for nested attribute sets
nix-repl> :p { = 1; = 2; }
{ foo = { bar = { baz = 1; buzz = 2; }; }; }

You will commonly see empty attr sets in nixpkgs, an example being:

  hello = callPackage ../applications/misc/hello { };


Technically, a derivation is just an attr set which has a few special attributes set to valid values which then nix can later realise into a build. Promotion from an attr set to derivation is facilitated through the builtins.derivation function. However directly calling the builtin is highly discouraged within nixpkgs. Instead people are encouraged to use stdenv.mkDerivation and other established builders which provide many good defaults to achieve their packaging goals.

If / Else logic

Like many other functional programming languages, you cannot use if without an accompanying else clause. This is because the expression needs to return a value, not just follow a code path.

  extension = if stdenv.isDarwin then

Note: The proper way to find the shared library extension within nixpkgs is hostPlatform.extensions.sharedLibrary.

Let expressions

Let expressions are a way to define values to be used later in a given 'in' scope. Generally these are used to alter a given value to conform to a slightly different format. Let expressions can refer to other values defined in the same let scope. For haskell users, let expressions work similarly to how they work in Haskell.

  src = let
    # e.g. 3.1-2 -> 3_1_2
    srcVersion = lib.strings.replaceStrings [ "." "-" ] [ "_" "_"] version;
    srcUrl = "${pname}-${srcVersion}.tar.gz";
  in fetchurl {
    url = srcUrl;
    sha256 = "...";

With expressions

With expressions allows for many values on an attr set to be exposed by their key names.

  # before
  meta = {
    licenses = lib.licenses.cc0;
    maintainers = [ lib.maintainers.jane lib.maintainers.joe ];
    platforms = lib.platforms.unix;
  # after
  meta = with lib; {
    licenses = licenses.cc0;
    maintainers = with maintainers; [ jane joe ];
    platforms = platforms.unix;


Many pure functional programming languages also have the feature that the evaluation model of the language is lazy. This means that the values of a data structure aren't computed until needed. The benefits for nix is that evaluating a package doesn't mean computing all packages, but only computing the dependency graph for the packages requested. In practice this means limiting the scope of an action from 80,000+ possible dependencies to just the dependencies explicitly mentioned by the nix expressions.

Although laziness isn't a hard requirement for nix to work. The purity model of nix makes laziness more a symptom rather than an explicit design goal. However, It does enable many implicit benefits such as memoization.