diff --git a/plugins/lsp/conform-nvim.nix b/plugins/lsp/conform-nvim.nix
index 87c6e861..c7ea1c1f 100644
--- a/plugins/lsp/conform-nvim.nix
+++ b/plugins/lsp/conform-nvim.nix
@@ -38,20 +38,28 @@ in {
         '';
 
       formatOnSave =
-        helpers.defaultNullOpts.mkNullable (types.submodule {
-          options = {
-            lspFallback = mkOption {
-              type = types.bool;
-              default = true;
-              description = "See :help conform.format for details.";
-            };
-            timeoutMs = mkOption {
-              type = types.int;
-              default = 500;
-              description = "See :help conform.format for details.";
-            };
-          };
-        })
+        helpers.defaultNullOpts.mkNullable
+        (
+          with types;
+            either
+            str
+            (
+              submodule {
+                options = {
+                  lspFallback = mkOption {
+                    type = types.bool;
+                    default = true;
+                    description = "See :help conform.format for details.";
+                  };
+                  timeoutMs = mkOption {
+                    type = types.int;
+                    default = 500;
+                    description = "See :help conform.format for details.";
+                  };
+                };
+              }
+            )
+        )
         "see documentation"
         ''
           If this is set, Conform will run the formatter on save.
@@ -96,10 +104,13 @@ in {
     setupOptions = with cfg;
       {
         formatters_by_ft = formattersByFt;
-        format_on_save = helpers.ifNonNull' formatOnSave {
-          lsp_fallback = formatOnSave.lspFallback;
-          timeout_ms = formatOnSave.timeoutMs;
-        };
+        format_on_save =
+          if builtins.isAttrs formatOnSave
+          then {
+            lsp_fallback = formatOnSave.lspFallback;
+            timeout_ms = formatOnSave.timeoutMs;
+          }
+          else helpers.mkRaw formatOnSave;
         format_after_save = helpers.ifNonNull' formatOnSave {
           lsp_fallback = formatOnSave.lspFallback;
         };
diff --git a/tests/test-sources/plugins/lsp/conform-nvim.nix b/tests/test-sources/plugins/lsp/conform-nvim.nix
index 5a59ed04..87a2772e 100644
--- a/tests/test-sources/plugins/lsp/conform-nvim.nix
+++ b/tests/test-sources/plugins/lsp/conform-nvim.nix
@@ -59,4 +59,39 @@
       };
     };
   };
+
+  custom_format_on_save_function = {
+    plugins.conform-nvim = {
+      enable = true;
+
+      formattersByFt = {
+        lua = ["stylua"];
+        python = ["isort" "black"];
+        javascript = [["prettierd" "prettier"]];
+        "*" = ["codespell"];
+        "_" = ["trimWhitespace"];
+      };
+
+      formatOnSave = ''
+        function(bufnr)
+          local ignore_filetypes = { "helm" }
+          if vim.tbl_contains(ignore_filetypes, vim.bo[bufnr].filetype) then
+            return
+          end
+
+          -- Disable with a global or buffer-local variable
+          if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
+            return
+          end
+
+          -- Disable autoformat for files in a certain path
+          local bufname = vim.api.nvim_buf_get_name(bufnr)
+          if bufname:match("/node_modules/") then
+            return
+          end
+          return { timeout_ms = 500, lsp_fallback = true }
+        end
+      '';
+    };
+  };
 }