Skip to content

Custom Interfaces

Client.UI = false turns the built-in console off. Client.Mount replaces it with your own:

luau
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:

luau
-- MyConsole.luau
return function(Api)
	local gui = buildMyConsole(Api)
	return function()
		gui:Destroy()
	end
end

The 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.

luau
Api.toggle()        -- flip
Api.toggle(true)    -- open
Api.toggle(false)   -- close

It 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:

luau
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
end

Without 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:

modulessize
UI = false (Client.Mount, or no interface)2175 KB
Built-in console109440 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:

luau
Api.setConfirmHandler(function(prompt)
	return showDialog(prompt)
end)

With no handler installed the answer is no, so destructive commands fail closed.

Released under the MIT Licence.