Hot Reload¶
Feather Hot Reload lets the desktop app send Lua source for a specific module over the existing WebSocket connection, then replace that module in the running game.
Warning
Hot reload is controlled remote code execution. Keep it disabled by default, use a narrow allowlist, and never enable it in production builds.
Caution
Do not copy the example config into a real project without changing the allowlist. Every allowed module can be replaced by whoever can send commands to that Feather session.
Before Enabling¶
Only enable hot reload when all of this is true:
- You are running a trusted development build.
- The Feather desktop app and game are on a trusted local machine or trusted LAN.
allownames only the modules you intend to edit.persistToDiskis off unless you deliberately want patches written into the LÖVE save directory.- You have explicitly installed and included the opt-in
hot-reloadplugin. - You have a clear release path where the
hot-reloadplugin is excluded or the Feather integration is removed.
Enable It¶
For CLI-managed projects, let Feather write the allowlist config:
For an already initialized project:
The VS Code extension uses the same CLI path. When you select the hot-reload plugin during init or plugin install, it prompts for Lua files and converts project paths like game/player.lua into module names like game.player.
Or configure it manually from feather.config.lua:
return {
debug = true,
-- Hot reload is an opt-in plugin. Without this include, Feather ignores
-- cmd:hot_reload messages even if debugger.hotReload is configured.
include = { "hot-reload" },
debugger = {
enabled = true,
hotReload = {
-- Development-only remote code execution. Keep this false unless you
-- understand that Feather can replace allowlisted Lua modules at runtime.
enabled = true,
-- Prefer exact modules. Avoid broad patterns unless you really mean it.
allow = {
"game.player",
"game.enemy",
"game.systems.*",
},
deny = {
"main",
"conf",
"feather.*",
},
persistToDisk = false,
clearOnBoot = false,
requireLocalNetwork = true,
showOverlay = true,
toastDuration = 2.5,
},
},
}
For manual plugin registration, require and register the plugin explicitly:
local FeatherPluginManager = require("feather.plugin_manager")
local HotReloadPlugin = require("plugins.hot-reload")
DEBUGGER = FeatherDebugger({
debug = true,
debugger = { enabled = true },
plugins = {
FeatherPluginManager.createPlugin(HotReloadPlugin, "hot-reload", {
enabled = true,
allow = { "game.player" },
deny = { "main", "conf", "feather.*" },
}),
},
})
Important
Installing Feather core alone does not enable hot reload. The hot-reload plugin must be installed and registered, which keeps the remote-code-loading path out of normal sessions.
| Field | Default | Description |
|---|---|---|
enabled |
false |
Enables hot reload commands for the session. |
allow |
{} |
Module allowlist. Supports exact names and prefix.* patterns. |
deny |
{} |
Explicit denylist. main, conf, and feather.* are always denied. |
persistToDisk |
false |
Writes hot patches to .feather/hot/<module>.lua in LÖVE's save directory. |
clearOnBoot |
false |
Clears persisted hot patches when Feather starts. |
requireLocalNetwork |
true |
Accepts reload commands only when the configured Feather host is local/LAN. |
showOverlay |
true |
Draws a temporary in-game toast when hot reload succeeds, fails, or restores. |
toastDuration |
2.5 |
Seconds the in-game hot reload toast remains visible. |
Important
allow = { "game.*" } is convenient, but a smaller list is safer. Prefer only the modules you are actively editing.
Warning
persistToDisk = true writes patched Lua source into .feather/hot in the save directory. That is useful for mobile development, but it also means a patch can survive an app restart until restored or cleared.
Use It¶
- Start the game with Feather enabled.
- Open Debugger.
- Open the game source folder if Feather cannot auto-detect it.
- Select a
.luafile from the file tree. - Press Reload.
The Debugger header shows the selected module next to Reload. If the file cannot be safely reloaded, the button stays disabled and its tooltip explains why, for example: hot reload is disabled, the plugin is missing, the selected file is not a Lua module, the module is protected, the module is not in hotReload.allow, or the session is blocked by requireLocalNetwork.
Compact status chips keep the current safety state visible:
Disabledmeans the plugin ordebugger.hotReload.enabledis off.Not allowlistedmeans the selected module does not matchhotReload.allow.Remote blockedmeansrequireLocalNetworkrejected the configured Feather host.PersistingmeanspersistToDiskis enabled and patches are written to the LÖVE save directory.Modified NandFailed Nsummarize modules replaced by Hot Reload or modules whose latest reload failed.
The Watch toggle polls the selected file and reloads it when the source changes. It intentionally watches only the selected module so changes are explicit and easy to reason about.
Tip
A file path like game/player.lua maps to the Lua module game.player. A folder entry like game/systems/init.lua maps to game.systems.
Example¶
From the repository root:
Then open Debugger, select example/hot_reload/gameplay.lua, edit the file, and press Reload. You can also enable Watch to reload the selected module whenever the file changes.
Note
The example allowlist contains exactly one module: example.hot_reload.gameplay. That narrow shape is intentional.
Rollback¶
Before replacing a module, Feather keeps the original package.loaded[module] value. Press Restore in the Debugger toolbar to restore all modules replaced by hot reload and clear persisted patches.
If a new module has a syntax error, runtime error, or failing migration hook, Feather restores the previous module immediately and reports the error in the app.
Migration Hook¶
Reloaded modules can expose __feather_reload:
local Player = {}
function Player.__feather_reload(newModule, oldModule)
newModule.instances = oldModule and oldModule.instances or {}
end
return Player
Use this for state migration, metatable updates, cache rebuilds, or system rebinding.
Mobile And Remote Devices¶
Hot reload works on mobile and handheld devices because Feather sends source code through WebSocket. When persistToDisk = true, patched modules are written to LÖVE's save directory using love.filesystem.write.
Note
This does not modify the application bundle. Persisted patches live under .feather/hot in the save directory and are meant for development sessions only.
Important
For mobile, Steam Deck, or second-computer workflows, only use hot reload on a trusted private network. Do not expose Feather's WebSocket port to public or shared networks.
Limits¶
Hot reload does not support native libraries, arbitrary filesystem writes, unrestricted Lua execution, reloading Feather internals, or replacing main.lua / conf.lua.