Custom Interfaces
Client.UI = false turns the built-in console off. Client.Mount replaces it with your own:
Operator.Start({
Commands = admin.Commands,
Client = {
Mount = admin.MyConsole,
ActivationKey = Enum.KeyCode.F2,
},
})Mount is a ModuleScript you own. It is cloned into the delivered bundle and required on the client once the first manifest has arrived, so the API it is handed is already populated:
-- MyConsole.luau
return function(Api)
local gui = buildMyConsole(Api)
return function()
gui:Destroy()
end
endThe function receives Client/Api and may return a teardown function. Api.parse, Api.getCommands, Api.getCommand and Api.getType are synchronous reads against the delivered manifest; Api.run(text) sends the raw text and waits for the server's result. Api.onManifest(fn) fires on every refresh so a permission change re-renders, and Api.onShutdown(fn) fires when access is revoked.
Why a hook rather than a path
This is why the hook exists rather than a documented path into PlayerGui. The bundle is created at runtime and destroyed when a player loses access, so there is no stable instance for a LocalScript of yours to require, and nothing for it to wait on. Handing your module into the bundle removes both problems.
Opening and closing it
Api.toggle() is how anything outside the interface opens it: a topbar icon, a settings menu, a keybind of your own.
Api.toggle() -- flip
Api.toggle(true) -- open
Api.toggle(false) -- closeIt returns whether the console is visible afterwards, and false if no interface is mounted.
The built-in console registers a handler when it mounts, so Api.toggle() drives it with no work from you. Your own interface should register one too, so the same call works against yours:
return function(Api)
local gui = buildMyConsole(Api)
Api.setToggleHandler(function(visible: boolean?): boolean
local target = if visible == nil then not gui.Enabled else visible
gui.Enabled = target
return target
end)
return function()
Api.setToggleHandler(nil)
gui:Destroy()
end
endWithout one, Api.toggle() returns false and does nothing. It never guesses at your instances.
The topbar icon
Operator draws its own topbar icon on keyboardless devices, using a vendored copy of TopbarPlus.
If your game already uses TopbarPlus, the two copies will not fight. TopbarPlus registers ReplicatedStorage.TopbarPlusReference and returns whichever copy loaded first, so the game gets one instance rather than two competing for the topbar.
That covers TopbarPlus against TopbarPlus only. A topbar button you built yourself will sit next to Operator's icon, because nothing tells us it exists. Set Client.Icon = false to turn Operator's icon off and call Api.toggle() from your own button.
You can also draw the icon yourself with Client.UI = true and Client.Icon = false: the console mounts as usual, Operator draws no icon, and Api.toggle() opens it from your code.
UI and Mount are exclusive
Setting Mount implies UI = false; writing UI = true as well is an error rather than a silent choice between them. With the built-in interface off, Client/UI is left out of the bundle entirely, along with its vendored Vide and TopbarPlus copies.
What reaches a player
Each authorised player receives a private clone, so this is per-player, not per-server:
| modules | size | |
|---|---|---|
UI = false (Client.Mount, or no interface) | 21 | 75 KB |
| Built-in console | 109 | 440 KB |
Client.Icon = false stops Operator requiring TopbarPlus, but the files still travel with the bundle.
Confirmations
A :destructive() command asks the player before running. Your interface answers through the handler it installs:
Api.setConfirmHandler(function(prompt)
return showDialog(prompt)
end)With no handler installed the answer is no, so destructive commands fail closed.