Skip to main content

Documentation Index

Fetch the complete documentation index at: https://sphere.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Setup

The index<> pattern against API.Types gives accurate types. Define it once at the top of every package script:
type Get<K> = index<
    typeof(require(script:FindFirstAncestor("Sphere").Core.API.Types))
, K>
Scripts not inside a named folder are treated as Client scripts by default. You may also create a folder named Client explicitly, it behaves identically.

Client

return function(Context: Get<"ClientContext">, ClientConstructor: Get<"Constructor">)
    --// ClientConstructor is now fully typed
end

Server

return function(Context: Get<"ServerContext">)
    --// no Constructor on the server
end

Shared

There are no restrictions on what they can do or return. Both Client and Server can access them via Context.Shared.
return {}
Require a Shared module from either context with:
local MyModule = require(Context.Shared.MyModule)
The full set of types exported by API.Types:
type ClientContext = {
    Permissions: {[string]: boolean},
    UIComponents: typeof(UIComponents),
    Shared: typeof(Sphere.Packages.Shared),
    Events: typeof(Sphere.Core.Events),
    Panel: typeof(Sphere),
}

type ServerContext = {
    Events: typeof(Sphere.Core.Events),
    Shared: typeof(Sphere.Packages.Shared),
    API: typeof(require(Sphere.Core.API)),
}

type Constructor = typeof(require(Constructor))

Package

Page

A page shows up as a tab in the UI and holds your sections and commands.
local Page = ClientConstructor:CreatePage("PageName", "rbxassetid://1")
--// OR
local Page = ClientConstructor:CreatePage("PageName", {
    Image = "rbxassetid://1",
    ImageRectSize = Vector2.zero,   --// Optional
    ImageRectOffset = Vector2.zero, --// Optional
})
Constructor:CreatePage(title: string, icon: string | Icon): Page
type Icon = {
    Image: string,
    ImageRectOffset: Vector2?,
    ImageRectSize: Vector2?,
}

type Page = {
    read Title: string,
    read UniqueId: string,
    read Icon: string | Icon,
    read ItemCount: number,

    read Sections: { [string]: Section },
    read Commands: { Command }?,

    read CreateSection: (self: Page, title: string, icon: Icon?, visibleGroup: boolean?) -> Section,
    read FindSectionByTitle: (self: Page, title: string) -> Section?,
    read Open: (self: Page) -> (),
}

Input

Title StringRequired
The name shown on the page tab.
Icon String | Icon
Asset ID or Icon table for the page icon.

Output

Title StringRead-Only
The page’s display name.
UniqueId StringRead-Only
Title lowercased. Used internally.
Icon String | IconRead-Only
The icon as passed.
Sections TableRead-Only
All sections on this page, keyed by lowercased title.
Commands TableRead-Only
Page-level commands in creation order. nil if empty.
CreateSection Function
Creates a new section on this page.
CreateCommand Function
Creates a command directly on this page, outside any section.
FindSectionByTitle Function
Returns the section matching the title, or nil.
Open Function
Switches the UI to this page. Pass true to suppress the selection event.

Section

Sections divide a page into labeled groups.
local Section = Page:CreateSection("SectionName")
--// OR with a header icon
local Section = Page:CreateSection("SectionName", {
    Image = "rbxassetid://1",
    ImageRectSize = Vector2.zero,
    ImageRectOffset = Vector2.zero,
})
--// OR with no visible group header
local Section = Page:CreateSection("SectionName", nil, false)
Page:CreateSection(title: string, icon: Icon?, visibleGroup: boolean?): Section
type Section = {
    read Page: Page,
    read Title: string,
    read UniqueId: string,
    read ItemCount: number,

    read Commands: { Command },
    read Frame: typeof(SectionPreset),

    read CreateCommand: <T>(self: Section, commandType: CommandType | T, display: Display, config: ConfigOf<T>, execute: ((...any) -> any)?) -> Command<T>,
    read FindCommandById: (self: Section, uniqueId: string) -> Command?,
}

Input

Title StringRequired
The label shown on the section header.
Icon Icon
Icon displayed on the section header. Omit to hide the icon slot.
VisibleGroup Boolean
Whether the section header is visible. Defaults to true.

Output

Title StringRead-Only
The section’s display name.
UniqueId StringRead-Only
Auto-generated GUID.
Page PageRead-Only
The page this section belongs to.
Commands TableRead-Only
All commands in this section, in creation order.
CreateCommand Function
Creates a new command inside this section.
FindCommandById Function
Returns the command matching the UniqueId, or nil.

Command

The type decides what UI element is rendered and what fields Config exposes.
Client
local Broadcast = Section:CreateCommand("TextBox",
    {Title = "Broadcast", Description = "Sends a message to all players in the server."},
    {Value = "Hello, world!", PlaceholderText = "Enter message..."},
    function(text: string)
        if text == "" then return end
        Broadcast.Config.Value = ""
        Context.Events.RemoteEvent:FireServer("Broadcast", text)
    end
)
Section:CreateCommand<T>(type: CommandType | T, display: Display, config: ConfigOf<T>, execute: ((...any) -> any)?): Command<T>
type Display = {
    Title: string,
    Description: string?,
}

type Command<T = CommandType> = {
    read Internal: {
        UniqueId: string,
        Type: T,
        Initialized: boolean,
        Section: Section?,
        Page: Page,
    },
    Display: Display,
    Config: ConfigOf<T>,
    read Execute: ((...any) -> any)?,

    read Clone: (self: Command<T>) -> Command<T>,
    read Destroy: (self: Command<T>) -> (),
}

Input

Type StringRequired
The command type. Determines the UI element and available Config fields.
Display TableRequired
Title is required. Description is optional.
Config Table
Initial config for the command. Fields depend on the type. Button has none.
Execute Function
Called when the command is triggered. Arguments depend on the type.

Output

Display Table
The display table as passed. Writable.
Config Table
The live config table. See Syncing for how changes propagate.
Execute FunctionRead-Only
The execute function as passed.
Internal TableRead-Only
  • UniqueId String Auto-generated GUID.
  • Type String The type passed at creation.
  • Section Section? The section this command belongs to, if any.
  • Page Page The page this command belongs to.
Clone Function
Shallow copy of this command. Config is cloned, execute is copied by reference.
Destroy Function
Removes the command.

Permissions

The framework does not enforce permissions. You decide what guards your UI and implement it yourself. A common pattern is a permit helper that closes over your context:
Client
return function(Context: Get<"ClientContext">, ClientConstructor: Get<"Constructor">)
    local hasEverything = Context.Permissions["*"]

    local function permit(key: string, fn: () -> ())
        if hasEverything or Context.Permissions[key] then
            fn()
        end
    end

    local Page = ClientConstructor:CreatePage("Admin", "rbxassetid://1")

    permit("KickPermission", function()
        Page:CreateCommand("Button",
            { Title = "Kick Player" },
            {},
            function() ... end
        )
    end)
end
"*" acts as a wildcard: if present, permit always passes. This is just one approach.
Anything inside a permit block that the user doesn’t have access to simply never gets created.

Input

Key StringRequired
The permission key to check against Context.Permissions. Use "*" as a wildcard that always passes.
Callback FunctionRequired
The block to run if the permission check passes. Any UI created inside is only registered when the check succeeds.

Output

Granted Boolean
If Context.Permissions[key] or "*" is truthy, the callback executes and the enclosed UI elements are created. Otherwise, nothing happens and no UI is registered.

Syncing

Only surface-level Config writes trigger UI updates. Mutating a nested table directly will not.

Server-only change

Fire your own RemoteEvent from the server and listen for it on the client.
Server
local Picks = {}

events.RemoteEvent.OnServerEvent:Connect(function(player: Player, type: string, referenceId: string, ...)
    if not API.CheckPermission(player, referenceId) then return end
    local value = ...
    if referenceId == "" and typeof(value) == "table" then
        Picks[player] = value
        Context.Events.RemoteEvent:FireAllClients("myCustomSyncEvent", value)
    end
end)
Client
Context.Events.RemoteEvent.OnClientEvent:Connect(function(eventName, value)
    if eventName == "myCustomSyncEvent" then
        Command.Config.Value = value
    end
end)
Per-client sync is not built-in. If you need to push a value to a specific client, implement your own replication.

Shared change

Writing to Config inside the execute function mirrors the new value across commands, updating the UI automatically.
Client.Lua
local TextBox
local Toggle = Section:CreateCommand("Toggle",
    { Title = "Toggle", Description = "Toggles and updates the readout." },
    { Value = false },
    function(value: boolean)
        Toggle.Config.Value = value
        TextBox.Config.Value = tostring(value)
    end
)
TextBox = Section:CreateCommand("TextBox",
    { Title = "Value" },
    { TextEditable = false, Value = tostring(Toggle.Config.Value) }
)