<section xmlns="http://docbook.org/ns/docbook"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xi="http://www.w3.org/2001/XInclude"
        version="5.0"
        xml:id="sec-freeform-modules">
 <title>Freeform modules</title>
 <para>
  Freeform modules allow you to define values for option paths that have not been declared explicitly. This can be used to add attribute-specific types to what would otherwise have to be <literal>attrsOf</literal> options in order to accept all attribute names.
 </para>
 <para>
  This feature can be enabled by using the attribute <literal>freeformType</literal> to define a freeform type. By doing this, all assignments without an associated option will be merged using the freeform type and combined into the resulting <literal>config</literal> set. Since this feature nullifies name checking for entire option trees, it is only recommended for use in submodules.
 </para>
 <example xml:id="ex-freeform-module">
  <title>Freeform submodule</title>
  <para>
   The following shows a submodule assigning a freeform type that allows arbitrary attributes with <literal>str</literal> values below <literal>settings</literal>, but also declares an option for the <literal>settings.port</literal> attribute to have it type-checked and assign a default value. See <xref linkend="ex-settings-typed-attrs"/> for a more complete example.
  </para>
 <programlisting>
{ lib, config, ... }: {

  options.settings = lib.mkOption {
    type = lib.types.submodule {

      freeformType = with lib.types; attrsOf str;

      # We want this attribute to be checked for the correct type
      options.port = lib.mkOption {
        type = lib.types.port;
        # Declaring the option also allows defining a default value
        default = 8080;
      };

    };
  };
}
 </programlisting>
 <para>
  And the following shows what such a module then allows
 </para>
 <programlisting>
{
  # Not a declared option, but the freeform type allows this
  settings.logLevel = "debug";

  # Not allowed because the the freeform type only allows strings
  # settings.enable = true;

  # Allowed because there is a port option declared
  settings.port = 80;

  # Not allowed because the port option doesn't allow strings
  # settings.port = "443";
}
 </programlisting>
 </example>
 <note>
  <para>
   Freeform attributes cannot depend on other attributes of the same set without infinite recursion:
<programlisting>
{
  # This throws infinite recursion encountered
  settings.logLevel = lib.mkIf (config.settings.port == 80) "debug";
}
</programlisting>
   To prevent this, declare options for all attributes that need to depend on others. For above example this means to declare <literal>logLevel</literal> to be an option.
  </para>
 </note>
</section>
