diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix
index 7ee9e1df9c1b..932d1196d36e 100644
--- a/modules/lib/maintainers.nix
+++ b/modules/lib/maintainers.nix
@@ -478,6 +478,12 @@
     github = "mainrs";
     githubId = 5113257;
   };
+  mikilio = {
+    name = "mikilio";
+    email = "official.mikilio+dev@gmail.com";
+    github = "mikilio";
+    githubId = 86004375;
+  };
   kmaasrud = {
     name = "Knut Magnus Aasrud";
     email = "km@aasrud.com";
diff --git a/modules/modules.nix b/modules/modules.nix
index 2aae09eb12d6..342ed06e2457 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -246,6 +246,7 @@ let
     ./programs/tmate.nix
     ./programs/tmux.nix
     ./programs/tofi.nix
+    ./programs/todoman.nix
     ./programs/topgrade.nix
     ./programs/translate-shell.nix
     ./programs/urxvt.nix
diff --git a/modules/programs/todoman.nix b/modules/programs/todoman.nix
new file mode 100644
index 000000000000..2e7ebf6a22b5
--- /dev/null
+++ b/modules/programs/todoman.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.todoman;
+
+  format = pkgs.formats.keyValue { };
+
+in {
+
+  meta.maintainers = [ hm.maintainers.mikilio ];
+
+  options.programs.todoman = {
+    enable = lib.mkEnableOption "todoman";
+
+    glob = mkOption {
+      type = types.str;
+      default = "*";
+      description = ''
+        The glob expansion which matches all directories relevant.
+      '';
+      example = "*/*";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Text for configuration of todoman.
+        The syntax is Python.
+
+        See [docs](`https://todoman.readthedocs.io/en/stable/man.html#id5`).
+        for the full list of options.
+      '';
+      example = ''
+        date_format = "%Y-%m-%d";
+        time_format = "%H:%M";
+        default_list = "Personal";
+        default_due = 48;
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = config.accounts.calendar ? basePath;
+      message = ''
+        A base directory for calendars must be specified via
+        `accounts.calendar.basePath` to generate config for todoman
+      '';
+    }];
+
+    home.packages = [ pkgs.todoman ];
+
+    xdg.configFile."todoman/config.py".text = lib.concatLines [
+      ''path = "${config.accounts.calendar.basePath}/${cfg.glob}"''
+      cfg.extraConfig
+    ];
+  };
+}
diff --git a/modules/programs/zsh.nix b/modules/programs/zsh.nix
index 50f8055c06f0..3e13404c5ee1 100644
--- a/modules/programs/zsh.nix
+++ b/modules/programs/zsh.nix
@@ -671,7 +671,7 @@ in
 
         ''
         ${optionalString cfg.prezto.enable
-            (builtins.readFile "${pkgs.zsh-prezto}/share/zsh-prezto/runcoms/zshrc")}
+            (builtins.readFile "${cfg.prezto.package}/share/zsh-prezto/runcoms/zshrc")}
 
         ${concatStrings (map (plugin: ''
           if [[ -f "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" ]]; then
diff --git a/modules/programs/zsh/prezto.nix b/modules/programs/zsh/prezto.nix
index 9a94f23856fa..5fdc8da23957 100644
--- a/modules/programs/zsh/prezto.nix
+++ b/modules/programs/zsh/prezto.nix
@@ -14,6 +14,8 @@ let
     options = {
       enable = mkEnableOption "prezto";
 
+      package = mkPackageOption pkgs "prezto" { default = "zsh-prezto"; };
+
       caseSensitive = mkOption {
         type = types.nullOr types.bool;
         # See <https://github.com/nix-community/home-manager/issues/2255>.
@@ -379,15 +381,15 @@ in {
   };
   config = mkIf cfg.enable (mkMerge [{
     home.file."${relToDotDir ".zprofile"}".text =
-      builtins.readFile "${pkgs.zsh-prezto}/share/zsh-prezto/runcoms/zprofile";
+      builtins.readFile "${cfg.package}/share/zsh-prezto/runcoms/zprofile";
     home.file."${relToDotDir ".zlogin"}".text =
-      builtins.readFile "${pkgs.zsh-prezto}/share/zsh-prezto/runcoms/zlogin";
+      builtins.readFile "${cfg.package}/share/zsh-prezto/runcoms/zlogin";
     home.file."${relToDotDir ".zlogout"}".text =
-      builtins.readFile "${pkgs.zsh-prezto}/share/zsh-prezto/runcoms/zlogout";
-    home.packages = with pkgs; [ zsh-prezto ];
+      builtins.readFile "${cfg.package}/share/zsh-prezto/runcoms/zlogout";
+    home.packages = [ cfg.package ];
 
     home.file."${relToDotDir ".zshenv"}".text =
-      builtins.readFile "${pkgs.zsh-prezto}/share/zsh-prezto/runcoms/zshenv";
+      builtins.readFile "${cfg.package}/share/zsh-prezto/runcoms/zshenv";
     home.file."${relToDotDir ".zpreztorc"}".text = ''
       # Generated by Nix
       ${optionalString (cfg.caseSensitive != null) ''
diff --git a/tests/modules/programs/todoman/config.nix b/tests/modules/programs/todoman/config.nix
new file mode 100644
index 000000000000..7323b7a43973
--- /dev/null
+++ b/tests/modules/programs/todoman/config.nix
@@ -0,0 +1,21 @@
+{
+  programs.todoman = {
+    enable = true;
+    glob = "*/*";
+    extraConfig = ''
+      date_format = "%d.%m.%Y"
+      default_list = "test"
+    '';
+  };
+
+  accounts.calendar.basePath = "base/path/calendar";
+
+  test.stubs = { todoman = { }; };
+
+  nmt.script = ''
+    configFile=home-files/.config/todoman/config.py
+    assertFileExists $configFile
+    assertFileContent $configFile ${./todoman-config-expected}
+  '';
+}
+
diff --git a/tests/modules/programs/todoman/default.nix b/tests/modules/programs/todoman/default.nix
new file mode 100644
index 000000000000..d429f9230d73
--- /dev/null
+++ b/tests/modules/programs/todoman/default.nix
@@ -0,0 +1 @@
+{ todoman-config = ./config.nix; }
diff --git a/tests/modules/programs/todoman/todoman-config-expected b/tests/modules/programs/todoman/todoman-config-expected
new file mode 100644
index 000000000000..00da13e1af89
--- /dev/null
+++ b/tests/modules/programs/todoman/todoman-config-expected
@@ -0,0 +1,3 @@
+path = "/home/hm-user/base/path/calendar/*/*"
+date_format = "%d.%m.%Y"
+default_list = "test"