# Options for Program Settings {#sec-settings-options}

Many programs have configuration files where program-specific settings
can be declared. File formats can be separated into two categories:

-   Nix-representable ones: These can trivially be mapped to a subset of
    Nix syntax. E.g. JSON is an example, since its values like
    `{"foo":{"bar":10}}` can be mapped directly to Nix:
    `{ foo = { bar = 10; }; }`. Other examples are INI, YAML and TOML.
    The following section explains the convention for these settings.

-   Non-nix-representable ones: These can\'t be trivially mapped to a
    subset of Nix syntax. Most generic programming languages are in this
    group, e.g. bash, since the statement `if true; then echo hi; fi`
    doesn\'t have a trivial representation in Nix.

    Currently there are no fixed conventions for these, but it is common
    to have a `configFile` option for setting the configuration file
    path directly. The default value of `configFile` can be an
    auto-generated file, with convenient options for controlling the
    contents. For example an option of type `attrsOf str` can be used
    for representing environment variables which generates a section
    like `export FOO="foo"`. Often it can also be useful to also include
    an `extraConfig` option of type `lines` to allow arbitrary text
    after the autogenerated part of the file.

## Nix-representable Formats (JSON, YAML, TOML, INI, \...) {#sec-settings-nix-representable}

By convention, formats like this are handled with a generic `settings`
option, representing the full program configuration as a Nix value. The
type of this option should represent the format. The most common formats
have a predefined type and string generator already declared under
`pkgs.formats`:

`pkgs.formats.json` { }

:   A function taking an empty attribute set (for future extensibility)
    and returning a set with JSON-specific attributes `type` and
    `generate` as specified [below](#pkgs-formats-result).

`pkgs.formats.yaml` { }

:   A function taking an empty attribute set (for future extensibility)
    and returning a set with YAML-specific attributes `type` and
    `generate` as specified [below](#pkgs-formats-result).

`pkgs.formats.ini` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \... }

:   A function taking an attribute set with values

    `listsAsDuplicateKeys`

    :   A boolean for controlling whether list values can be used to
        represent duplicate INI keys

    `listToValue`

    :   A function for turning a list of values into a single value.

    It returns a set with INI-specific attributes `type` and `generate`
    as specified [below](#pkgs-formats-result).

`pkgs.formats.toml` { }

:   A function taking an empty attribute set (for future extensibility)
    and returning a set with TOML-specific attributes `type` and
    `generate` as specified [below](#pkgs-formats-result).

::: {#pkgs-formats-result}
These functions all return an attribute set with these values:
:::

`type`

:   A module system type representing a value of the format

`generate` *`filename jsonValue`*

:   A function that can render a value of the format to a file. Returns
    a file path.

    ::: {.note}
    This function puts the value contents in the Nix store. So this
    should be avoided for secrets.
    :::

::: {#ex-settings-nix-representable .example}
::: {.title}
**Example: Module with conventional `settings` option**
:::
The following shows a module for an example program that uses a JSON
configuration file. It demonstrates how above values can be used, along
with some other related best practices. See the comments for
explanations.

```nix
{ options, config, lib, pkgs, ... }:
let
  cfg = config.services.foo;
  # Define the settings format used for this program
  settingsFormat = pkgs.formats.json {};
in {

  options.services.foo = {
    enable = lib.mkEnableOption "foo service";

    settings = lib.mkOption {
      # Setting this type allows for correct merging behavior
      type = settingsFormat.type;
      default = {};
      description = ''
        Configuration for foo, see
        <link xlink:href="https://example.com/docs/foo"/>
        for supported settings.
      '';
    };
  };

  config = lib.mkIf cfg.enable {
    # We can assign some default settings here to make the service work by just
    # enabling it. We use `mkDefault` for values that can be changed without
    # problems
    services.foo.settings = {
      # Fails at runtime without any value set
      log_level = lib.mkDefault "WARN";

      # We assume systemd's `StateDirectory` is used, so we require this value,
      # therefore no mkDefault
      data_path = "/var/lib/foo";

      # Since we use this to create a user we need to know the default value at
      # eval time
      user = lib.mkDefault "foo";
    };

    environment.etc."foo.json".source =
      # The formats generator function takes a filename and the Nix value
      # representing the format value and produces a filepath with that value
      # rendered in the format
      settingsFormat.generate "foo-config.json" cfg.settings;

    # We know that the `user` attribute exists because we set a default value
    # for it above, allowing us to use it without worries here
    users.users.${cfg.settings.user} = { isSystemUser = true; };

    # ...
  };
}
```
:::

### Option declarations for attributes {#sec-settings-attrs-options}

Some `settings` attributes may deserve some extra care. They may need a
different type, default or merging behavior, or they are essential
options that should show their documentation in the manual. This can be
done using [](#sec-freeform-modules).

We extend above example using freeform modules to declare an option for
the port, which will enforce it to be a valid integer and make it show
up in the manual.

::: {#ex-settings-typed-attrs .example}
::: {.title}
**Example: Declaring a type-checked `settings` attribute**
:::
```nix
settings = lib.mkOption {
  type = lib.types.submodule {

    freeformType = settingsFormat.type;

    # Declare an option for the port such that the type is checked and this option
    # is shown in the manual.
    options.port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = ''
        Which port this service should listen on.
      '';
    };

  };
  default = {};
  description = ''
    Configuration for Foo, see
    <link xlink:href="https://example.com/docs/foo"/>
    for supported values.
  '';
};
```
:::
