Custom Argument Types
When the built-in argument types and inline enums aren't enough, you can register custom argument types with _K.Registry.registerType. Custom types integrate directly with the Command Bar's autocomplete, validation indicator, and argument parser.
Register Types in Shared Addons
Just like Custom Commands, always register custom argument types inside a Shared addon (no "Server" or "Client" in the ModuleScript name) so both the client Command Bar and the server command processor can validate and parse the type.
Quick Helpers
Before writing a full parser from scratch, check if one of the two built-in type factories fits your needs:
1. Enum Types (_K.Registry.makeEnumType)
_K.Registry.makeEnumType(typeName, enumItems) builds a complete ArgumentType with fuzzy autocomplete and validation from a list of strings or EnumItems:
return function(_K)
local classes = { "Warrior", "Mage", "Rogue", "Paladin", "Ranger" }
local classType = _K.Registry.makeEnumType("class", classes)
-- Register singular ("class") and comma-separated listable ("classes") variants:
_K.Registry.registerType("class", classType)
_K.Registry.registerType("classes", { listable = true }, classType)
end2. Sequence Types (_K.Registry.makeSequenceType)
_K.Registry.makeSequenceType(definition) builds a comma-delimited sequence type (like vector3 or vector2):
return function(_K)
local udim2Type = _K.Registry.makeSequenceType({
length = 4,
transform = tonumber,
validate = function(value, index)
if value == nil then
return false, `Invalid number at component {index} in UDim2`
end
return true
end,
constructor = UDim2.new,
})
_K.Registry.registerType("udim2", udim2Type)
endFull Custom Type Registration (_K.Registry.registerType)
For dynamic game objects (such as vehicles, zones, quests, or inventory items), pass an ArgumentType table to _K.Registry.registerType(name, typeObject, override?).
How the Argument Pipeline Works
When a user types an argument, src/Shared/Process/Argument.luau processes each token through 4 stages (transform → validate → parse → postParse):
transform(rawText: string) -> (any, string?)(Optional): Pre-processes the raw token (e.g.,string.lowerortonumber). Returningnil(for a non-optional argument) immediately invalidates the argument with the optional second return string as the error message.validate(value: any, self: Argument) -> (boolean, string?)(Required): Returnstrueifvalueis valid, orfalse, "Error reason"to mark the argument invalid in the Command Bar and block execution.parse(value: any, self: Argument) -> (any, string?)(Required): Converts the validated value into the final Luau value passed tocommand.run(context, ...). Returningnil, "Error reason"(or throwing an error) aborts parsing.postParse(parsedArgs: { any }, self: Argument) -> { any }(Optional): Transforms the array of parsed values for listable or prefix-expanded types before unpacking intorun.
ArgumentType Schema Reference
type ArgumentType = {
displayName: string?, -- Type name shown in Command Bar tooltips (defaults to registered name)
listable: boolean?, -- When true, splits comma-separated inputs ("a,b") and returns an array
filterLog: boolean?, -- When true, filters the argument in server command logs
freeText: boolean?, -- Marks open-ended user text so live logs mask it as "…" for other players
prefixes: { [string]: string }?, -- Maps Lua pattern prefixes (e.g. ["^all$"] = "allItems") to sub-types
transform: ((text: string) -> (any, string?))?, -- Pre-processes raw string input
validate: (value: any, self: any) -> (boolean, string?), -- Validates transformed input
parse: (value: any, self: any) -> (any, string?), -- Produces the final value passed to command.run
postParse: ((parsedArgs: { any }, self: any) -> any)?, -- Post-processes parsed array
suggestions: ((text: string, from: number, definition: ArgumentDefinition) -> ({ any }, any?))?,
log: ((self: any) -> string)?, -- Optional custom serializer for command logs
}Autocomplete suggestions Format
The suggestions(text, from, definition) function returns a list of suggestions for the Command Bar. Each entry in the returned table can be:
- A plain
string(e.g."SportsCar"). - An
Instance(usesinstance.Name). - A tuple table
{ matchOrAliases, displayLabel?, value? }:matchOrAliases: Astring(or{ string }array of aliases) inserted when the user pressesTab.displayLabel: Optional formatted label shown in the suggestion dropdown (defaults tomatchOrAliases).
Complete Example: Dynamic "vehicle" and "vehicles" Type
Below is a complete shared addon that registers "vehicle" and "vehicles" argument types backed by a folder of vehicle models in ReplicatedStorage.Vehicles, along with a ;spawnvehicle command:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
return function(_K)
local vehiclesFolder = ReplicatedStorage:WaitForChild("Vehicles")
local function findVehicle(query: string): Model?
-- 1. Check exact match first
for _, vehicle in vehiclesFolder:GetChildren() do
if string.lower(vehicle.Name) == query then
return vehicle :: Model
end
end
-- 2. Fall back to prefix match
for _, vehicle in vehiclesFolder:GetChildren() do
if string.find(string.lower(vehicle.Name), query, 1, true) == 1 then
return vehicle :: Model
end
end
return nil
end
local vehicleType = {
displayName = "vehicle",
transform = string.lower,
validate = function(query: string): (boolean, string?)
if findVehicle(query) ~= nil then
return true
end
return false, `Invalid vehicle "{query}"`
end,
parse = function(query: string): (Model?, string?)
local vehicle = findVehicle(query)
if not vehicle then
return nil, "Vehicle not found"
end
return vehicle
end,
suggestions = function(_text: string, _from: number)
return _K.Util.Suggest.new(vehiclesFolder:GetChildren())
end,
}
-- Register singular ("vehicle" -> Model) and plural ("vehicles" -> { Model }) types
_K.Registry.registerType("vehicle", vehicleType)
_K.Registry.registerType("vehicles", { listable = true }, vehicleType)
-- Use the new type in a custom command!
_K.Registry.registerCommand(_K, {
name = "spawnvehicle",
aliases = { "car", "sv" },
description = "Spawns a vehicle next to one or more players.",
group = "General",
args = {
{
type = "players",
name = "Players",
description = "The player(s) to spawn the vehicle for.",
},
{
type = "vehicle",
name = "Vehicle",
description = "The vehicle model to spawn.",
},
},
run = function(_context, players: { Player }, vehicleTemplate: Model)
for _, player in players do
local character = player.Character
if character and character.PrimaryPart then
local clone = vehicleTemplate:Clone()
clone:PivotTo(character:GetPivot() * CFrame.new(0, 0, -10))
clone.Parent = workspace
end
end
end,
})
end