diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9bd93e1b6..f8ae353c5 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -91,7 +91,8 @@
 
 /modules/programs/mcfly.nix                           @marsam
 
-/modules/programs/mpv.nix                             @tadeokondrak
+/modules/programs/mpv.nix                             @tadeokondrak @thiagokokada
+/tests/modules/programs/mpv                           @thiagokokada
 
 /modules/programs/mu.nix                              @KarlJoad
 
@@ -123,6 +124,9 @@
 
 /modules/programs/powerline-go.nix                    @DamienCassou
 
+/modules/programs/rofi.nix                            @thiagokokada
+/tests/modules/programs/rofi                          @thiagokokada
+
 /modules/programs/rofi-pass.nix                       @seylerius
 /tests/modules/programs/rofi-pass                     @seylerius
 
diff --git a/doc/release-notes/rl-2103.adoc b/doc/release-notes/rl-2103.adoc
index 599af75f2..db33e0cfe 100644
--- a/doc/release-notes/rl-2103.adoc
+++ b/doc/release-notes/rl-2103.adoc
@@ -50,6 +50,28 @@ As a result of this change, <<opt-programs.mpv.package>> is no longer the
 resulting derivation. Use the newly introduced `programs.mpv.finalPackage`
 instead.
 
+* The <<opt-programs.rofi.extraConfig>> option is now an attrset rather
+than a string. To migrate, move the each line into the attrset,
+removing the `rofi.` prefix from the keys. For example,
++
+[source,nix]
+----
+programs.rofi.extraConfig = ''
+  rofi.show-icons: true
+  rofi.modi: drun,emoji,ssh
+'';
+----
++
+becomes
++
+[source,nix]
+----
+programs.rofi.extraConfig = {
+  show-icons = true;
+  modi = "drun,emoji,ssh";
+};
+----
+
 [[sec-release-21.03-state-version-changes]]
 === State Version Changes
 
diff --git a/modules/programs/rofi.nix b/modules/programs/rofi.nix
index 734bcc423..f7b4682be 100644
--- a/modules/programs/rofi.nix
+++ b/modules/programs/rofi.nix
@@ -71,21 +71,9 @@ let
     };
   };
 
-  valueToString = value:
-    if isBool value then (if value then "true" else "else") else toString value;
-
   windowColorsToString = window:
     concatStringsSep ", " (with window; [ background border separator ]);
 
-  rowsColorsToString = rows: ''
-    ${optionalString (rows.normal != null)
-    (setOption "color-normal" (rowColorsToString rows.normal))}
-    ${optionalString (rows.active != null)
-    (setOption "color-active" (rowColorsToString rows.active))}
-    ${optionalString (rows.urgent != null)
-    (setOption "color-urgent" (rowColorsToString rows.urgent))}
-  '';
-
   rowColorsToString = row:
     concatStringsSep ", " (with row; [
       background
@@ -95,14 +83,45 @@ let
       highlight.foreground
     ]);
 
-  setOption = name: value:
-    optionalString (value != null) "rofi.${name}: ${valueToString value}";
+  mkColorScheme = colors:
+    if colors != null then
+      with colors; {
+        color-window =
+          if (window != null) then (windowColorsToString window) else null;
+        color-normal = if (rows != null && rows.normal != null) then
+          (rowColorsToString rows.normal)
+        else
+          null;
+        color-active = if (rows != null && rows.active != null) then
+          (rowColorsToString rows.active)
+        else
+          null;
+        color-urgent = if (rows != null && rows.active != null) then
+          (rowColorsToString rows.urgent)
+        else
+          null;
+      }
+    else
+      { };
 
-  setColorScheme = colors:
-    optionalString (colors != null) ''
-      ${optionalString (colors.window != null) setOption "color-window"
-      (windowColorsToString colors.window)}
-      ${optionalString (colors.rows != null) (rowsColorsToString colors.rows)}
+  mkValueString = value:
+    if isBool value then
+      if value then "true" else "false"
+    else if isInt value then
+      toString value
+    else
+      ''"${toString value}"'';
+
+  mkKeyValue = name: value: "${name}: ${mkValueString value};";
+
+  toRasi = section: config:
+    let
+      # Remove null values so the resulting config does not have empty lines
+      configStr = generators.toKeyValue { inherit mkKeyValue; }
+        (attrsets.filterAttrs (m: v: v != null) config);
+    in ''
+      ${section} {
+      ${configStr}}
     '';
 
   locationsMap = {
@@ -119,12 +138,12 @@ let
 
   themeName = if (cfg.theme == null) then
     null
-  else if (lib.isString cfg.theme) then
+  else if (isString cfg.theme) then
     cfg.theme
   else
-    lib.removeSuffix ".rasi" (baseNameOf cfg.theme);
+    removeSuffix ".rasi" (baseNameOf cfg.theme);
 
-  themePath = if (lib.isString cfg.theme) then null else cfg.theme;
+  themePath = if (isString cfg.theme) then null else cfg.theme;
 
 in {
   options.programs.rofi = {
@@ -220,7 +239,7 @@ in {
 
     location = mkOption {
       default = "center";
-      type = types.enum (builtins.attrNames locationsMap);
+      type = types.enum (attrNames locationsMap);
       description = "The location rofi appears on the screen.";
     };
 
@@ -284,15 +303,22 @@ in {
     };
 
     configPath = mkOption {
-      default = "${config.xdg.configHome}/rofi/config";
-      defaultText = "$XDG_CONFIG_HOME/rofi/config";
+      default = "${config.xdg.configHome}/rofi/config.rasi";
+      defaultText = "$XDG_CONFIG_HOME/rofi/config.rasi";
       type = types.str;
       description = "Path where to put generated configuration file.";
     };
 
     extraConfig = mkOption {
-      default = "";
-      type = types.lines;
+      default = { };
+      example = literalExample ''
+        {
+          modi = "drun,emoji,ssh";
+          kb-primary-paste = "Control+V,Shift+Insert";
+          kb-secondary-paste = "Control+v,Insert";
+        }
+      '';
+      type = with types; attrsOf (oneOf [ int str bool ]);
       description = "Additional configuration to add.";
     };
 
@@ -308,31 +334,29 @@ in {
 
     home.packages = [ cfg.package ];
 
-    home.file."${cfg.configPath}".text = ''
-      ${setOption "width" cfg.width}
-      ${setOption "lines" cfg.lines}
-      ${setOption "font" cfg.font}
-      ${setOption "bw" cfg.borderWidth}
-      ${setOption "eh" cfg.rowHeight}
-      ${setOption "padding" cfg.padding}
-      ${setOption "separator-style" cfg.separator}
-      ${setOption "hide-scrollbar"
-      (if (cfg.scrollbar != null) then (!cfg.scrollbar) else cfg.scrollbar)}
-      ${setOption "terminal" cfg.terminal}
-      ${setOption "cycle" cfg.cycle}
-      ${setOption "fullscreen" cfg.fullscreen}
-      ${setOption "location" (builtins.getAttr cfg.location locationsMap)}
-      ${setOption "xoffset" cfg.xoffset}
-      ${setOption "yoffset" cfg.yoffset}
-
-      ${setColorScheme cfg.colors}
-      ${setOption "theme" themeName}
-
-      ${cfg.extraConfig}
-    '';
+    home.file."${cfg.configPath}".text = toRasi "configuration" ({
+      width = cfg.width;
+      lines = cfg.lines;
+      font = cfg.font;
+      bw = cfg.borderWidth;
+      eh = cfg.rowHeight;
+      padding = cfg.padding;
+      separator-style = cfg.separator;
+      hide-scrollbar =
+        if (cfg.scrollbar != null) then (!cfg.scrollbar) else null;
+      terminal = cfg.terminal;
+      cycle = cfg.cycle;
+      fullscreen = cfg.fullscreen;
+      location = (getAttr cfg.location locationsMap);
+      xoffset = cfg.xoffset;
+      yoffset = cfg.yoffset;
+      theme = themeName;
+    } // (mkColorScheme cfg.colors) // cfg.extraConfig);
 
     xdg.dataFile = mkIf (themePath != null) {
       "rofi/themes/${themeName}.rasi".source = themePath;
     };
   };
+
+  meta.maintainers = with maintainers; [ thiagokokada ];
 }
diff --git a/tests/modules/programs/rofi/default.nix b/tests/modules/programs/rofi/default.nix
index c18c3b0ed..03b9049e6 100644
--- a/tests/modules/programs/rofi/default.nix
+++ b/tests/modules/programs/rofi/default.nix
@@ -1,3 +1,4 @@
 {
+  rofi-valid-config = ./valid-config.nix;
   rofi-assert-on-both-theme-and-colors = ./assert-on-both-theme-and-colors.nix;
 }
diff --git a/tests/modules/programs/rofi/valid-config-expected.rasi b/tests/modules/programs/rofi/valid-config-expected.rasi
new file mode 100644
index 000000000..23cf24606
--- /dev/null
+++ b/tests/modules/programs/rofi/valid-config-expected.rasi
@@ -0,0 +1,20 @@
+configuration {
+bw: 1;
+color-normal: "argb:58455a64, #fafbfc, argb:58455a64, #00bcd4, #fafbfc";
+color-window: "argb:583a4c54, argb:582a373e, #c3c6c8";
+cycle: false;
+eh: 1;
+font: "Droid Sans Mono 14";
+hide-scrollbar: false;
+kb-primary-paste: "Control+V,Shift+Insert";
+kb-secondary-paste: "Control+v,Insert";
+lines: 10;
+location: 0;
+modi: "drun,emoji,ssh";
+padding: 400;
+separator-style: "solid";
+terminal: "/some/path";
+width: 100;
+xoffset: 0;
+yoffset: 0;
+}
diff --git a/tests/modules/programs/rofi/valid-config.nix b/tests/modules/programs/rofi/valid-config.nix
new file mode 100644
index 000000000..8fb66a7b8
--- /dev/null
+++ b/tests/modules/programs/rofi/valid-config.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  config = {
+    programs.rofi = {
+      enable = true;
+      width = 100;
+      lines = 10;
+      borderWidth = 1;
+      rowHeight = 1;
+      padding = 400;
+      font = "Droid Sans Mono 14";
+      scrollbar = true;
+      terminal = "/some/path";
+      separator = "solid";
+      cycle = false;
+      fullscren = true;
+      colors = {
+        window = {
+          background = "argb:583a4c54";
+          border = "argb:582a373e";
+          separator = "#c3c6c8";
+        };
+
+        rows = {
+          normal = {
+            background = "argb:58455a64";
+            foreground = "#fafbfc";
+            backgroundAlt = "argb:58455a64";
+            highlight = {
+              background = "#00bcd4";
+              foreground = "#fafbfc";
+            };
+          };
+        };
+      };
+      window = {
+        background = "background";
+        border = "border";
+        separator = "separator";
+      };
+      extraConfig = {
+        modi = "drun,emoji,ssh";
+        kb-primary-paste = "Control+V,Shift+Insert";
+        kb-secondary-paste = "Control+v,Insert";
+      };
+    };
+
+    nmt.script = ''
+      assertFileContent \
+        home-files/.config/rofi/config.rasi \
+        ${./valid-config-expected.rasi}
+    '';
+  };
+}