Console / REPL¶
The Console is an opt-in plugin that lets you evaluate arbitrary Lua code inside the running game from the Feather desktop app. It is not included by default and must be explicitly enabled.
Warning
Do not include the Console plugin in builds shipped to users. It allows remote code execution and can be a serious security risk.
Setup¶
With auto.lua¶
require("feather.auto").setup({
include = { "console" },
pluginOptions = {
console = {
evalEnabled = true,
showOverlay = true,
toastDuration = 2.5,
},
},
})
Manual setup¶
local FeatherDebugger = require "feather"
local FeatherPluginManager = require "feather.plugin_manager"
local ConsolePlugin = require "feather.plugins.console"
local debugger = FeatherDebugger({
debug = true,
apiKey = "my-secret-key", -- required for eval to work
plugins = {
FeatherPluginManager.createPlugin(ConsolePlugin, "console", {
evalEnabled = true,
showOverlay = true,
toastDuration = 2.5,
}),
},
})
function love.update(dt)
debugger:update(dt)
end
Security:
apiKeymust be set and non-empty in the game config and in the Feather desktop app. The desktop uses the active session API key override when set, otherwise it falls back to the default Settings → API Key field. The Console refuses to execute any code if the keys don't match.
Usage¶
Open the Console tab in the Feather desktop app. Type any Lua expression or statement and press Enter to run it.
| Key | Action |
|---|---|
Enter |
Execute |
Shift+Enter |
Insert newline (multiline input) |
↑ / ↓ |
Recall previous commands |
Ctrl+R |
Search command history |
The Console header shows the current execution gates: session connection, plugin enabled/disabled, API key presence, and sandbox state when the plugin reports it. Disabled controls include tooltips that explain what is missing before eval can run.
Each transcript entry includes a status badge, timestamp, and compact actions:
- Copy the command or result.
- Put the command back into the editor.
- Run the command again.
- Expand long print/result output while keeping full-copy behavior intact.
- Inspect structured table return values, copy fields, insert field paths, and pin expressions into Observability.
The Snippets panel stores reusable Lua commands per Feather session. You can save the current editor input, insert a snippet without running it, run a snippet directly, rename saved snippets, or delete them. When no snippets have been saved yet, Feather shows built-in snippets for common checks like graphics stats, memory usage, frame timing, and window size.
Press Refresh _G to fetch a shallow list of runtime global names for editor autocomplete. This is manual, not automatic, so Feather only snapshots globals when you ask. The snapshot includes names and Lua types, not inspected values.
Result inspectors and pins¶
When a command returns a table, Console now sends a shallow structured preview in addition to the existing inspected string. Expand the result to browse top-level fields, copy values, insert a return object.field path back into the editor, or pin that path.
Pinned expressions are evaluated by the Console plugin while the session is alive and published to Observability as console.<name> keys. Pins are live debugging helpers; unpin them from the Console sidebar when you no longer need them.
Read-only guardrails¶
The Read-only toggle sends eval requests with best-effort mutation guardrails. It blocks obvious assignments and known mutating calls before the code runs, while keeping the normal sandbox enabled.
This is not a true dry run or rollback system. Lua code can still hide side effects behind function calls, metatables, or game APIs. Treat it as a comfort rail for quick inspection, not as a security boundary.
What gets captured¶
- Return values — serialized with
inspect()for readable table output. print()calls — captured and shown inline below the command, even in sandbox mode.
_G access¶
In the default sandbox, Console code can read through _G, so globals like love, player, or world are available if the game exposes them. Bare assignments such as foo = 1 stay inside the temporary sandbox table. To intentionally mutate a game global, write _G.foo = 1 or configure sandbox = false for trusted development sessions.
Examples¶
Inspect live state¶
-- Read a value
return player.health
-- Inspect a whole table (one level deep)
return player
-- Nested field
return world.enemies[1].position
Tweak values at runtime¶
-- Teleport the player
player.x = 100
player.y = 200
-- Refill health
player.health = player.maxHealth
-- Speed up the game
love.timer.sleep = function() end
Call game functions¶
-- Trigger a function defined in your game
spawnEnemy("goblin", 300, 400)
-- Play a sound
sfx.hit:play()
-- Reload a level
gameState:loadLevel(1)
Inspect love2d internals¶
-- Draw call stats
return love.graphics.getStats()
-- Memory usage
return collectgarbage("count") .. " KB"
-- Screen size
return love.graphics.getDimensions()
Multi-line scripts¶
Use Shift+Enter to write multi-line code:
local total = 0
for _, enemy in ipairs(world.enemies) do
total = total + enemy.health
end
return total
Print multiple values¶
print("pos:", player.x, player.y)
print("state:", player.state)
return player.velocity
-- Output:
-- pos: 142 320
-- state: jumping
-- { x = 2.5, y = -8.1 }
Options¶
| Option | Type | Default | Description |
|---|---|---|---|
evalEnabled |
boolean |
false |
Must be true to allow code execution. Acts as a second safety gate alongside apiKey. |
sandbox |
boolean |
true |
Run code in a sandboxed environment that inherits _G but isolates print(). Set to false to run in the real global environment (allows mutating globals directly). |
maxCodeSize |
number |
20000 |
Maximum characters per eval payload. Payloads over this limit are rejected. |
instructionLimit |
number |
100000 |
Lua instruction count before the eval is aborted. Prevents infinite loops from freezing the game. |
maxOutputSize |
number |
100000 |
Maximum characters in the serialized return value before it is truncated. |
showOverlay |
boolean |
true |
Draws a temporary in-game toast when console eval succeeds or is rejected. |
toastDuration |
number |
2.5 |
Seconds the in-game console toast remains visible. |
Security¶
Caution
Never enable the Console in builds you ship to players. Anyone who knows the host IP, port, and apiKey can execute arbitrary Lua code inside your game process.
Tip
If you are using Feather via the CLI for development only, you don't need to do anything, since feather code is not included in when you create your production builds.
The plugin enforces multiple layers of protection:
- Opt-in only —
evalEnabledmust be explicitly set totrue. - API key authentication — The
apiKeysent from the desktop must exactly match the one configured on theFeatherDebuggerinstance. Empty or missing keys are rejected. - Code size limit — Incoming code exceeding
maxCodeSizeis rejected before compilation. - Instruction limit — A
debug.sethookinstruction counter aborts execution if the limit is exceeded, preventing infinite loops and runaway code. - Output truncation — Return values larger than
maxOutputSizeare truncated. - Sandboxed environment — By default, code runs in a sandbox that inherits
_Gbut overridesprint()to capture output. Setsandbox = falseto run code directly in the game's_Gcontext.
Recommended setup for integrated development:
local debugger = FeatherDebugger({
debug = Config.IS_DEBUG, -- disabled in release builds
apiKey = os.getenv("FEATHER_KEY") or "dev-only",
})
Set FEATHER_KEY in your shell and match it in Feather desktop Settings → API Key. This way the key is never committed to source control and the Console is inert in release builds.
See Recommendations → Level 3 — Exclude from the release build for ways to strip Feather entirely from release builds.
How It Works¶
- The desktop app sends a
cmd:evalmessage containing{ code, id, apiKey }. - Feather's
__handleCommandroutes the message toConsolePlugin:handleEval(). - The plugin validates auth, compiles the code via
loadstring, and executes it in a sandboxed environment. print()calls inside the eval are captured and returned alongside the result.- An
eval:responsemessage is sent back with{ status, result, prints }plus optional structured result metadata for inspectors.