Skip to main content

Per-command configuration

Every command receives a props.Configuration instance, a small in-memory key-value store scoped to that command, this plugin session.

Run = function(props)
local count = props.Configuration:Get("RunCount", 0)
props.Configuration:Set("RunCount", count + 1)
props:SendNotification(string.format("Run %d times this session", count + 1))
end

Why this exists

It's tempting to reach for a module-level local instead:

local runCount = 0 -- DON'T rely on this surviving between executions

return {
Run = function(props)
runCount += 1
props:SendNotification(string.format("Run %d times", runCount))
end,
}

That counter resets to 0 on every Execute press. Reason: the plugin requires your command via RequireNoCache — it clones the ModuleScript into a scratch container, requires the clone, runs Run, then throws the clone away. Roblox's normal require cache (which is what would ordinarily preserve module-level state across invocations) never gets populated, because the clone is a different Instance every time.

Configuration is the workaround: it lives on the plugin's ScriptHandler singleton, outside any cloned module. So :Get / :Set survive across Run invocations because they're not stored on the script itself.

If you ever migrate a command off CommandRunner and into a plain ModuleScript that's required normally, you can replace Configuration with module-level locals and they'll behave the same way. Inside CommandRunner specifically, Configuration is the only correct place to put cross-Run state.

API

MethodReturnsNotes
props.Configuration:Get(key, default)anydefault is returned when key is unset (default itself defaults to nil).
props.Configuration:Set(key, value)nilStores value under key.

Lifetime

Configuration is in-memory only. It survives:

  • Multiple Execute presses within one widget session.
  • Switching between commands and back.

It does not survive:

  • Plugin reload (Studio restart, "Disable plugin" → "Enable plugin").
  • The widget being closed and reopened, actually it does survive widget toggle, since the Configuration instance lives on the ScriptHandler singleton, not on the UI. But treat it as session-scoped to be safe.

If you need durable per-command settings, use props.Plugin:SetSetting(key, value) directly. It's namespaced per-plugin and persists across sessions.

When to reach for it

Good fits:

  • A counter that resets when the user reloads the plugin.
  • A "previous value" cache so a command can compute a delta.
  • Memoizing an expensive computation across multiple Execute presses.

Bad fits:

  • Anything you'd be sad to lose on plugin reload, use plugin:SetSetting for that.
  • Anything that needs to be visible across commands, Configuration is per-command.

A worked example

A toggle that remembers its last "on" timestamp so you can compute idle time:

return {
Arguments = {
Toggle = {
Type = "switch",
Default = false,
Run = function(props, value)
if value then
props.Configuration:Set("LastOnAt", os.clock())
else
local on = props.Configuration:Get("LastOnAt")
if on then
props:SendNotification(
string.format("Was on for %.1fs", os.clock() - on)
)
end
end
end,
},
},

DisableExecuteButton = true,
Run = function() end,
}