diff --git a/nixos/doc/manual/development/settings-options.xml b/nixos/doc/manual/development/settings-options.xml
new file mode 100644
index 00000000000..84895adb444
--- /dev/null
+++ b/nixos/doc/manual/development/settings-options.xml
@@ -0,0 +1,179 @@
+
+ Options for Program Settings
+
+
+ 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, ...)
+
+ 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.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.ini { listsAsDuplicateKeys ? false, ... }
+
+
+
+ A function taking an attribute set with values
+
+
+
+ listsAsDuplicateKeys
+
+
+
+ A boolean for controlling whether list values can be used to represent duplicate INI keys
+
+
+
+
+ It returns a set with INI-specific attributes type and generate as specified below.
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+ These functions all return an attribute set with these values:
+
+
+
+ type
+
+
+
+ A module system type representing a value of the format
+
+
+
+
+
+ generatefilenamejsonValue
+
+
+
+ A function that can render a value of the format to a file. Returns a file path.
+
+
+ This function puts the value contents in the Nix store. So this should be avoided for secrets.
+
+
+
+
+
+
+
+
+ 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.
+
+
+{ 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 values.
+ '';
+ };
+ };
+
+ 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} = {}
+
+ # ...
+ };
+}
+
+
+
+
+
diff --git a/nixos/doc/manual/development/writing-modules.xml b/nixos/doc/manual/development/writing-modules.xml
index bbf793bb0be..602f134f9cb 100644
--- a/nixos/doc/manual/development/writing-modules.xml
+++ b/nixos/doc/manual/development/writing-modules.xml
@@ -183,4 +183,5 @@ in {
+