7 min read

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:

  1. Enable the CCL beta through File ⟩ Beta Features ⟩ AvatarAbilities Character Controller Library.

  2. From the Avatar tab, open Avatar Settings.

    Avatar Settings indicated in Studio's toolbar
  3. Select the Movement tab on the left side of the window and, in the Abilities section, select Character Controller Library.

    Character Controller Library toggle in the Avatar Settings window
  4. 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.

  1. Create a ModuleScript inside ReplicatedStorage/CustomAbilities (a Folder).

  2. Rename it to Dash as a unique identity.

  3. Paste the following supporting code into the new Dash script:

    ```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:
  • Sensor.Ground — The character is standing on the ground (no air-dashing).
  • Not("DashCooldown") — The DashCooldown label is absent (Not() takes exactly one label, not a group).
`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`.
  • InputName = "Dash" specifies the input the ability listens to.
  • Mode = "Press" tells the ability to activate when the input is pressed.
  • ActionSlot defines the input's action slot.
`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:

  1. StartsWhen = All( Sensor.Ground, Not("DashCooldown") ) — Assuming the character is on the ground and not in a dash cooldown period, TimedLabels.OnStart broadcasts the DashWindow label for 0.2 seconds.
  2. RunsWhile = "DashWindow" keeps the dash running.
  3. After 0.2 seconds, the DashWindow label expires, so RunsWhile = "DashWindow" becomes false and the ability stops (no need to include an OnStop callback function).
  4. TimedLabels.OnStop broadcasts the DashCooldown label for 1.0 seconds, and because StartsWhen contains a Not("DashCooldown") condition, players cannot dash again during this cooldown.
  5. Once the DashCooldown label 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:

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:

  1. Place a new Script inside ServerScriptService.
  2. Rename it to RegisterCustomAbilities (this script can be used to register multiple abilities in a loop).
  3. 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 calling LuaGlobals.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)