diff --git a/modules/services/window-managers/bspwm/default.nix b/modules/services/window-managers/bspwm/default.nix
index f20c01221..84a75e3a4 100644
--- a/modules/services/window-managers/bspwm/default.nix
+++ b/modules/services/window-managers/bspwm/default.nix
@@ -61,21 +61,23 @@ in {
 
     home.packages = [ cfg.package ];
 
-    xdg.configFile."bspwm/bspwmrc".source = pkgs.writeShellScript "bspwmrc" ''
-      ${concatStringsSep "\n" (mapAttrsToList formatMonitor cfg.monitors)}
+    xdg.configFile."bspwm/bspwmrc".source = pkgs.writeShellScript "bspwmrc"
+      ((optionalString (cfg.extraConfigEarly != "")
+        (cfg.extraConfigEarly + "\n")) + ''
+          ${concatStringsSep "\n" (mapAttrsToList formatMonitor cfg.monitors)}
 
-      ${concatStringsSep "\n" (mapAttrsToList formatSetting cfg.settings)}
+          ${concatStringsSep "\n" (mapAttrsToList formatSetting cfg.settings)}
 
-      bspc rule -r '*'
-      ${concatStringsSep "\n" (mapAttrsToList formatRule cfg.rules)}
+          bspc rule -r '*'
+          ${concatStringsSep "\n" (mapAttrsToList formatRule cfg.rules)}
 
-      # java gui fixes
-      export _JAVA_AWT_WM_NONREPARENTING=1
-      bspc rule -a sun-awt-X11-XDialogPeer state=floating
+          # java gui fixes
+          export _JAVA_AWT_WM_NONREPARENTING=1
+          bspc rule -a sun-awt-X11-XDialogPeer state=floating
 
-      ${cfg.extraConfig}
-      ${concatMapStringsSep "\n" formatStartupProgram cfg.startupPrograms}
-    '';
+          ${cfg.extraConfig}
+          ${concatMapStringsSep "\n" formatStartupProgram cfg.startupPrograms}
+        '');
 
     # for applications not started by bspwm, e.g. sxhkd
     xsession.profileExtra = ''
diff --git a/modules/services/window-managers/bspwm/options.nix b/modules/services/window-managers/bspwm/options.nix
index 441b49682..848e991bd 100644
--- a/modules/services/window-managers/bspwm/options.nix
+++ b/modules/services/window-managers/bspwm/options.nix
@@ -187,6 +187,13 @@ in {
       '';
     };
 
+    extraConfigEarly = mkOption {
+      type = types.lines;
+      default = "";
+      description =
+        "Like extraConfig, except commands are run at the start of the config file.";
+    };
+
     monitors = mkOption {
       type = types.attrsOf (types.listOf types.str);
       default = { };
diff --git a/modules/services/window-managers/i3-sway/sway.nix b/modules/services/window-managers/i3-sway/sway.nix
index ad8432652..725fac7d3 100644
--- a/modules/services/window-managers/i3-sway/sway.nix
+++ b/modules/services/window-managers/i3-sway/sway.nix
@@ -263,60 +263,61 @@ let
   seatStr = moduleStr "seat";
 
   configFile = pkgs.writeText "sway.conf" (concatStringsSep "\n"
-    ((if cfg.config != null then
-      with cfg.config;
-      ([
-        (fontConfigStr fonts)
-        "floating_modifier ${floating.modifier}"
-        (windowBorderString window floating)
-        "hide_edge_borders ${window.hideEdgeBorders}"
-        "focus_wrapping ${lib.hm.booleans.yesNo focus.forceWrapping}"
-        "focus_follows_mouse ${focus.followMouse}"
-        "focus_on_window_activation ${focus.newWindow}"
-        "mouse_warping ${
-          if builtins.isString (focus.mouseWarping) then
-            focus.mouseWarping
-          else if focus.mouseWarping then
-            "output"
-          else
-            "none"
-        }"
-        "workspace_layout ${workspaceLayout}"
-        "workspace_auto_back_and_forth ${
-          lib.hm.booleans.yesNo workspaceAutoBackAndForth
-        }"
-        "client.focused ${colorSetStr colors.focused}"
-        "client.focused_inactive ${colorSetStr colors.focusedInactive}"
-        "client.unfocused ${colorSetStr colors.unfocused}"
-        "client.urgent ${colorSetStr colors.urgent}"
-        "client.placeholder ${colorSetStr colors.placeholder}"
-        "client.background ${colors.background}"
-        (keybindingsStr {
-          keybindings = keybindingDefaultWorkspace;
-          bindsymArgs =
-            lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
-        })
-        (keybindingsStr {
-          keybindings = keybindingsRest;
-          bindsymArgs =
-            lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
-        })
-        (keycodebindingsStr keycodebindings)
-      ] ++ mapAttrsToList inputStr input
-        ++ mapAttrsToList outputStr output # outputs
-        ++ mapAttrsToList seatStr seat # seats
-        ++ mapAttrsToList (modeStr cfg.config.bindkeysToCode) modes # modes
-        ++ mapAttrsToList assignStr assigns # assigns
-        ++ map barStr bars # bars
-        ++ optional (gaps != null) gapsStr # gaps
-        ++ map floatingCriteriaStr floating.criteria # floating
-        ++ map windowCommandsStr window.commands # window commands
-        ++ map startupEntryStr startup # startup
-        ++ map workspaceOutputStr workspaceOutputAssign # custom mapping
-      )
-    else
-      [ ]) ++ (optional cfg.systemdIntegration ''
-        exec "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP; systemctl --user start sway-session.target"'')
+    ((optional (cfg.extraConfigEarly != "") cfg.extraConfigEarly)
+      ++ (if cfg.config != null then
+        with cfg.config;
+        ([
+          (fontConfigStr fonts)
+          "floating_modifier ${floating.modifier}"
+          (windowBorderString window floating)
+          "hide_edge_borders ${window.hideEdgeBorders}"
+          "focus_wrapping ${lib.hm.booleans.yesNo focus.forceWrapping}"
+          "focus_follows_mouse ${focus.followMouse}"
+          "focus_on_window_activation ${focus.newWindow}"
+          "mouse_warping ${
+            if builtins.isString (focus.mouseWarping) then
+              focus.mouseWarping
+            else if focus.mouseWarping then
+              "output"
+            else
+              "none"
+          }"
+          "workspace_layout ${workspaceLayout}"
+          "workspace_auto_back_and_forth ${
+            lib.hm.booleans.yesNo workspaceAutoBackAndForth
+          }"
+          "client.focused ${colorSetStr colors.focused}"
+          "client.focused_inactive ${colorSetStr colors.focusedInactive}"
+          "client.unfocused ${colorSetStr colors.unfocused}"
+          "client.urgent ${colorSetStr colors.urgent}"
+          "client.placeholder ${colorSetStr colors.placeholder}"
+          "client.background ${colors.background}"
+          (keybindingsStr {
+            keybindings = keybindingDefaultWorkspace;
+            bindsymArgs =
+              lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
+          })
+          (keybindingsStr {
+            keybindings = keybindingsRest;
+            bindsymArgs =
+              lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
+          })
+          (keycodebindingsStr keycodebindings)
+        ] ++ mapAttrsToList inputStr input
+          ++ mapAttrsToList outputStr output # outputs
+          ++ mapAttrsToList seatStr seat # seats
+          ++ mapAttrsToList (modeStr cfg.config.bindkeysToCode) modes # modes
+          ++ mapAttrsToList assignStr assigns # assigns
+          ++ map barStr bars # bars
+          ++ optional (gaps != null) gapsStr # gaps
+          ++ map floatingCriteriaStr floating.criteria # floating
+          ++ map windowCommandsStr window.commands # window commands
+          ++ map startupEntryStr startup # startup
+          ++ map workspaceOutputStr workspaceOutputAssign # custom mapping
+        )
+      else
+        [ ]) ++ (optional cfg.systemdIntegration ''
+          exec "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP; systemctl --user start sway-session.target"'')
       ++ (optional (!cfg.xwayland) "xwayland disable") ++ [ cfg.extraConfig ]));
 
   defaultSwayPackage = pkgs.sway.override {
@@ -425,6 +426,13 @@ in {
       description =
         "Extra configuration lines to add to ~/.config/sway/config.";
     };
+
+    extraConfigEarly = mkOption {
+      type = types.lines;
+      default = "";
+      description =
+        "Like extraConfig, except lines are added to ~/.config/sway/config before all other configuration.";
+    };
   };
 
   config = mkIf cfg.enable (mkMerge [
diff --git a/tests/modules/services/window-managers/bspwm/bspwmrc b/tests/modules/services/window-managers/bspwm/bspwmrc
index 2f669b830..ccc070088 100755
--- a/tests/modules/services/window-managers/bspwm/bspwmrc
+++ b/tests/modules/services/window-managers/bspwm/bspwmrc
@@ -1,3 +1,5 @@
+extra config early
+
 if [[ $(bspc query --desktops --names --monitor 'focused') == Desktop ]]; then
   bspc monitor 'focused' -d 'desktop 1' 'd'\''esk top'
 fi
diff --git a/tests/modules/services/window-managers/bspwm/configuration.nix b/tests/modules/services/window-managers/bspwm/configuration.nix
index 577645bfa..49c82f6fe 100644
--- a/tests/modules/services/window-managers/bspwm/configuration.nix
+++ b/tests/modules/services/window-managers/bspwm/configuration.nix
@@ -24,6 +24,9 @@ with lib;
         border = null;
         unknownRule = 42;
       };
+      extraConfigEarly = ''
+        extra config early
+      '';
       extraConfig = ''
         extra config
       '';
diff --git a/tests/modules/services/window-managers/sway/default.nix b/tests/modules/services/window-managers/sway/default.nix
index 7cc948ca4..f11ef7e38 100644
--- a/tests/modules/services/window-managers/sway/default.nix
+++ b/tests/modules/services/window-managers/sway/default.nix
@@ -1,6 +1,7 @@
 {
   sway-bar-focused-colors = ./sway-bar-focused-colors.nix;
-  sway-bindkeys-to-code = ./sway-bindkeys-to-code.nix;
+  sway-bindkeys-to-code-and-extra-config =
+    ./sway-bindkeys-to-code-and-extra-config.nix;
   sway-default = ./sway-default.nix;
   sway-followmouse = ./sway-followmouse.nix;
   sway-followmouse-legacy = ./sway-followmouse-legacy.nix;
diff --git a/tests/modules/services/window-managers/sway/sway-bindkeys-to-code.conf b/tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.conf
similarity index 98%
rename from tests/modules/services/window-managers/sway/sway-bindkeys-to-code.conf
rename to tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.conf
index 975bf2a89..9029febf5 100644
--- a/tests/modules/services/window-managers/sway/sway-bindkeys-to-code.conf
+++ b/tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.conf
@@ -1,3 +1,5 @@
+import $HOME/.cache/wal/colors-sway
+
 font pango:monospace 8.000000
 floating_modifier Mod1
 default_border pixel 2
@@ -104,3 +106,4 @@ bar {
 }
 
 exec "/nix/store/00000000000000000000000000000000-dbus/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP; systemctl --user start sway-session.target"
+exec_always pkill flashfocus; flasfocus &
diff --git a/tests/modules/services/window-managers/sway/sway-bindkeys-to-code.nix b/tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.nix
similarity index 69%
rename from tests/modules/services/window-managers/sway/sway-bindkeys-to-code.nix
rename to tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.nix
index 0c3929256..0817837ac 100644
--- a/tests/modules/services/window-managers/sway/sway-bindkeys-to-code.nix
+++ b/tests/modules/services/window-managers/sway/sway-bindkeys-to-code-and-extra-config.nix
@@ -9,11 +9,17 @@
     # overriding findutils causes issues
     config.menu = "${pkgs.dmenu}/bin/dmenu_run";
     config.bindkeysToCode = true;
+    extraConfigEarly = ''
+      import $HOME/.cache/wal/colors-sway
+    '';
+    extraConfig = ''
+      exec_always pkill flashfocus; flasfocus &
+    '';
   };
 
   nmt.script = ''
     assertFileExists home-files/.config/sway/config
     assertFileContent $(normalizeStorePaths home-files/.config/sway/config) \
-      ${./sway-bindkeys-to-code.conf}
+      ${./sway-bindkeys-to-code-and-extra-config.conf}
   '';
 }