Source: Roblox Creator Hub · CC BY 4.0 · View source · Code samples: MIT Imported 2026-10-03. Formatting adapted for this site.
Custom abilities
This guide outlines how to add a custom dash ability for all player characters, where activating the ability speeds the character forward in the direction it's facing, followed by a short cooldown before players can dash again.
Enable CCL
The CCL is opt-in through Studio's Avatar Settings window. To enable it:
Enable the CCL beta through File ⟩ Beta Features ⟩ AvatarAbilities Character Controller Library.
From the Avatar tab, open Avatar Settings.
Select the Movement tab on the left side of the window and, in the Abilities section, select Character Controller Library.
All of the standard abilities like Running, Jumping, and Climbing are enabled by default. To disable any of them at runtime, uncheck the associated box.
Note
It's not recommended to disable **Running**, as doing so will prevent characters from moving along the ground. Additionally, you should always keep **Getting Up** enabled if **Falling Down** is enabled, as a mismatch will allow characters to fall down (trip) but never get back up. Ability module
The first step in authoring a custom ability is to create an AbilityDefinition inside a ModuleScript that can be shared between the server and client.
Create a
ModuleScriptinsideReplicatedStorage/CustomAbilities(aFolder).Rename it to
Dashas a unique identity.
Paste the following supporting code into the new
Dashscript:```lua local AvatarAbilities = require("@rbx/AvatarAbilities") local Identifiers = AvatarAbilities.Identifiers local Rule = AvatarAbilities.Rule local Sensor = Identifiers.Sensor local All, Any, Not = Rule.All, Rule.Any, Rule.Not ```
Ability definition
The core behavior of any ability is defined through its AbilityDefinition (line 8+), a Luau table that defines its identity, conditions, behavior, and lifecycle.
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Any, Not = Rule.All, Rule.Any, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
Labels = { "Dashing" },
StartsWhen = All( Sensor.Ground, Not("DashCooldown") ),
RunsWhile = "DashWindow",
Input = {
InputName = "Dash",
Mode = "Press",
ActionSlot = 5
},
TimedLabels = {
OnStart = { DashWindow = 0.2 }, -- Seconds the dash stays "active"
OnStop = { DashCooldown = 1.0 }, -- Seconds until characters can dash again
},
}
function Dash.OnStart(managerCtx: AvatarAbilities.ManagerContext, abilityCtx: AvatarAbilities.AbilityContext)
local rootPart = managerCtx.AbilityOwner.PrimaryPart
if rootPart then
rootPart:ApplyImpulse(managerCtx.RootLookVector * 80 * rootPart.AssemblyMass)
end
end
return Dash The following table outlines every parameter in the dash ability's AbilityDefinition table:
| Field | Description |
|---|---|
| `Name` | The ability name that other abilities can reference in conditions and conflicts. Multiple ability definitions can use the same name. The [`ModuleScript`](/docs/modulescript) name, not this field, determines the unique [`Configuration`](/docs/configuration) key under the character. |
| `Labels` | A [label](/docs/roblox-characters-character-controller-library#labels) is a named bit in a shared world mask which acts as the coordination bus between abilities. Essentially, an active ability broadcasts its `Labels` to the world mask, while ability [conditions](/docs/roblox-characters-character-controller-library#conditions) (`StartsWhen` or `RunsWhile`) test the world mask and react. Abilities never call each other; they merely broadcast labels and react to labels. |
| `StartsWhen` | One or more [conditions](/docs/roblox-characters-character-controller-library#conditions) which are checked every frame while the ability is **inactive**; when they're all `true`, the ability can start. The notation `All()` means that **all** of the nested conditions must be `true`, specifically:
|
| `RunsWhile` | One or more [conditions](/docs/roblox-characters-character-controller-library#conditions) which are checked every frame while the ability is **active**; the moment they stop being `true`, the ability stops. The sole condition `RunsWhile = "DashWindow"` means the ability keeps running while the `DashWindow` label exists. Defining `RunsWhile` replaces the generated input condition, so releasing the input doesn't cancel this dash. For a `Hold` or `Toggle` ability that must stop when its input becomes inactive, include the `Rule.Input` sentinel in the custom condition. |
| `Input` | The [input](/docs/roblox-characters-character-controller-library#inputs) which triggers the ability. The CCL always adds it to `StartsWhen`.
|
| `TimedLabels` | Timed labels appear for a fixed number of seconds and expire on their own, independent of whether the ability is still running. `OnStart`/`OnStop` broadcast their [labels](/docs/roblox-characters-character-controller-library#labels) when the ability starts/stops, respectively. When the associated duration ends, the label(s) expire. |
Collectively, StartsWhen, RunsWhile, and TimedLabels form the dash ability's entire loop:
StartsWhen = All( Sensor.Ground, Not("DashCooldown") )— Assuming the character is on the ground and not in a dash cooldown period,TimedLabels.OnStartbroadcasts theDashWindowlabel for0.2seconds.RunsWhile = "DashWindow"keeps the dash running.- After
0.2seconds, theDashWindowlabel expires, soRunsWhile = "DashWindow"becomesfalseand the ability stops (no need to include anOnStopcallback function). TimedLabels.OnStopbroadcasts theDashCooldownlabel for1.0seconds, and becauseStartsWhencontains aNot("DashCooldown")condition, players cannot dash again during this cooldown.- Once the
DashCooldownlabel expires, everything resets automatically and players can attempt another dash.
Following the AbilityDefinition table, the OnStart callback function runs once at the moment the ability activates. This is where the actual dash happens. The function's first parameter, managerCtx, is a ManagerContext object with multiple properties, including:
managerCtx.AbilityOwner— The characterModel, such thatmanagerCtx.AbilityOwner.PrimaryPartis the root part.managerCtx.RootLookVector— The direction the character is facing.
Dash only needs this one callback, as the impulse is applied in a single instant and the timed labels handle the rest. See callbacks for info on OnUpdate, OnStop, OnSetup, and OnTeardown.
Ability registration
Registration of custom abilities for each player character must occur on the server:
- Place a new
ScriptinsideServerScriptService. - Rename it to
RegisterCustomAbilities(this script can be used to register multiple abilities in a loop). - Paste the following code into the script. Note that abilities are added per-character by passing the ability module as the second parameter of
addAbilityForCharacter(), not by callingLuaGlobals.require()on the module.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AvatarAbilities = require("@rbx/AvatarAbilities")
local CUSTOM_ABILITIES = {
ReplicatedStorage.CustomAbilities.Dash,
-- ...
}
local function onCharacterAdded(character: Model)
local actor = character:WaitForChild("AbilityManagerActor", 10)
if not actor then return end
while not actor:IsDescendantOf(game) do actor.AncestryChanged:Wait() end
for _, abilityModule in CUSTOM_ABILITIES do
AvatarAbilities.addAbilityForCharacter(character, abilityModule)
end
end
local function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(onCharacterAdded)
if player.Character then task.spawn(onCharacterAdded, player.Character) end
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
onPlayerAdded(player)
end Although registration occurs on the server, ability callbacks run in both the predicted client simulation and the authoritative server simulation. Keep callback behavior deterministic so both simulations produce the same result.
Input definition
As noted in ability definition, the Input definition specifies how the ability is activated. Its ActionSlot field defines an action slot which is associated with a list of InputActions and InputBindings.
The CCL always adds the generated input sensor to StartsWhen. If you omit RunsWhile, it also uses that sensor as the continuation condition. Defining RunsWhile replaces the default, so include the Rule.Input value directly when a Hold or Toggle ability must stop with its input. For an example, see inputs.
Several action slot input bindings are predefined by Roblox and, in the future, the Input Action Manager will allow you to reconfigure default input bindings for action slots as desired. Setting ActionSlot to 0 will choose the next available empty slot. On mobile devices, slots 1-7 populate to buttons on the screen (see diagram below).
| Slot | Keyboard & Mouse | Gamepad | Touch | Default Assignment |
|---|---|---|---|---|
| `1` | [`Space`](/docs/enum-keycode#space) | [`ButtonA`](/docs/enum-keycode#buttona) | ① | Jump |
| `2` | [`LeftShift`](/docs/enum-keycode#leftshift) | [`ButtonL1`](/docs/enum-keycode#buttonl1) | ② | Sprint |
| `3` | [`LeftControl`](/docs/enum-keycode#leftcontrol) | [`ButtonB`](/docs/enum-keycode#buttonb) | ③ | Crouch |
| `4` | [`R`](/docs/enum-keycode#r) | [`ButtonX`](/docs/enum-keycode#buttonx) | ④ | |
| `5` | [`MouseLeftButton`](/docs/enum-keycode#mouseleftbutton) | [`ButtonR2`](/docs/enum-keycode#buttonr2) | ⑤ | |
| `6` | [`Q`](/docs/enum-keycode#q) | [`ButtonY`](/docs/enum-keycode#buttony) | ⑥ | |
| `7` | [`X`](/docs/enum-keycode#x) | [`ButtonR1`](/docs/enum-keycode#buttonr1) | ⑦ | |
| `8` | [`C`](/docs/enum-keycode#c) | [`ButtonL2`](/docs/enum-keycode#buttonl2) | ||
| `9` | [`F`](/docs/enum-keycode#f) | [`DPadLeft`](/docs/enum-keycode#dpadleft) | ||
| `10` | [`G`](/docs/enum-keycode#g) | [`DPadRight`](/docs/enum-keycode#dpadright) | ||
| `11` | [`V`](/docs/enum-keycode#v) | [`DPadDown`](/docs/enum-keycode#dpaddown) |