{ lib, ... }: let inherit (lib) types mkOption; attrsOfModule = mkOption { type = types.attrsOf ( types.submodule { options.bar = mkOption { type = types.int; }; } ); }; listOfModule = mkOption { type = types.listOf ( types.submodule { options.bar = mkOption { type = types.int; }; } ); }; in { imports = [ # Module A { options.attrsOfModule = attrsOfModule; options.mergedAttrsOfModule = attrsOfModule; options.listOfModule = listOfModule; options.mergedListOfModule = listOfModule; } # Module B { options.mergedAttrsOfModule = attrsOfModule; options.mergedListOfModule = listOfModule; } # Values # It is important that the value is defined in a separate module # Without valueMeta the actual value and sub-options wouldn't be accessible via: # options.attrsOfModule.type.getSubOptions { attrsOfModule = { foo.bar = 42; }; mergedAttrsOfModule = { foo.bar = 42; }; } ( { options, ... }: { config.listOfModule = [ { bar = 42; } ]; config.mergedListOfModule = [ { bar = 42; } ]; # Result options to expose the list module to bash as plain attribute path options.listResult = mkOption { default = (builtins.head options.listOfModule.valueMeta.list).configuration.options.bar.value; }; options.mergedListResult = mkOption { default = (builtins.head options.mergedListOfModule.valueMeta.list).configuration.options.bar.value; }; } ) ]; }