Events

Scripting on Roblox is primarily event-driven. The engine supports multiple types of events. When implementing your logic, you can connect functions to built-in events fired by the engine to respond to them. You can also create custom events that you fire and respond to. Additionally, you can use networking events that allow event-driven communication across the client-server boundary.

Built-in Events

Many objects have built-in events provided by their APIs that automatically respond to specific actions or changes related to those objects. For example, a Player.Character touching a Part automatically fires a BasePart.Touched event. Most built-in events are synchronous, so you can connect a function for following custom behaviors in response to a certain built-in event by scripting.

Connecting Functions to Events

You can connect a function to an event using Connect() to execute code each time the event fires. Most events pass arguments to their connected functions when they fire. For example, the BasePart.Touched event passes the object that touched the Part, and the Players.PlayerAdded event passes the Player that joined your experience.

It's the best practice to name the function with the pattern onEventName to help you find the function in the future. The following code sample demonstrates how to connect a function named onPartTouched to the BasePart.Touched event of a Part in the Workspace.


-- Script in ServerScriptService
local part = workspace.Part
local function onPartTouched(object)
print("Part was touched by", object:GetFullName())
end
part.Touched:Connect(onPartTouched)

You can connect anonymous functions to events when you want to use variables in the parent scope and don't need to use the function elsewhere. The following code sample shows how to connect an anonymous function to the Players.PlayerAdded event.


-- Script in ServerScriptService
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print(player.Name, " joined the game!")
end)

Disconnecting Functions from Events

In Luau, the RBXScriptSignal data type represents events. The RBXScriptSignal:Connect() method returns an RBXScriptConnection object. If you connect a function to an event but don't want the function to call in subsequent event firings, disconnect the function from the event by calling the RBXScriptConnection:Disconnect() method.

When Luau destroys an event's object, such as the Player object when a user leaves the experience, all of its connections disconnect automatically. The following code sample shows how to connect and disconnect a function to the Part.Touched event.


-- Script in ServerScriptService
local part = workspace.Part
local targetPart = workspace.TargetPart
-- Declare the variable before defining the function so it can be disconnected from within the function
local connection
local function onPartTouched(otherPart)
if otherPart == targetPart then
print("The part hit the target")
connection:Disconnect()
end
end
connection = part.Touched:Connect(onPartTouched)

Waiting for Events to Fire

If you want a script to yield or pause until a specific event fires, use the RBXScriptSignal.Wait() function.


-- Script in ServerScriptService
local part = workspace.Part
local touchedPart = part.Touched:Wait()
print("The part was touched by", touchedPart:GetFullName())

The RBXScriptSignal.Wait() function returns the event's arguments. You can assign these arguments to variables where RBXScriptSignal.Wait() is called.


-- Script in ServerScriptService
local VirtualInputManager = game:GetService("VirtualInputManager")
local isPressed, keyCode, isRepeatedKey, layerCollector = VirtualInputManager.SendKeyEvent:Wait()
print(isPressed)
print(keyCode)
--..etc

Custom Events

Custom events allow you to bind behaviors between scripts and communicate your specific desired outcome for certain in-experience actions. Custom events can be both asynchronous and synchronous. and they can only communicate scripts within the same side of the client-server model.

Custom Asynchronous Events

BindableEvent allows asynchronous, one-way communication between scripts. You can use it to define a custom event and fire it manually by calling its BindableEvent:Fire() method without yielding for return values. The connected function receives arguments that you pass to Fire.

Creating Custom Asynchronous Events

To create a new BindableEvent in the Explorer:

  1. Hover over the container in the Explorer into which you want to insert a BindableEvent. It's common to put BindableEvents in an Event folder in ServerScriptService to use them with Scripts and ReplicatedStorage to use them with LocalScripts.
  2. Click the button that appears to the right of the container to open the Insert Object menu.
  3. Select BindableEvent.
  4. Rename the event to describe its purpose using PascalCase.

To create a new BindableEvent in a Script, use Instance.new():


-- Script in ServerScriptService
local roundStartedEvent = Instance.new("BindableEvent")
roundStartedEvent.Name = RoundStarted
roundStartedEvent.Parent = ServerScriptService

Using Custom Asynchronous Events

You can connect multiple functions to the same BindableEvent, but Luau executes them in an unpredictable order. To ensure that functions execute in a particular order, combine their bodies or calls into a single function to connect to the event.

To fire a BindableEvent in ServerScriptService named ShareData:


-- Script in ServerScriptService named DataSender
local ServerScriptService = game:GetService("ServerScriptService")
local shareData = ServerScriptService.ShareData
local HELLO_WORLD = "Hello world!"
-- Shares HELLO_WORLD after 2 seconds
task.wait(2)
shareData:Fire(HELLO_WORLD)

To connect to a BindableEvent in ServerScriptService named ShareData:


-- Script in ServerScriptService named DataReceiver
local ServerScriptService = game:GetService("ServerScriptService")
local shareData = ServerScriptService.ShareData
shareData.Event:Connect(function(data)
print(data)
end)

Custom Synchronous Events

BindableFunction objects allow for synchronous, two-way communication between scripts. They contain an OnInvoke callback that you can define in one script and call from other scripts. The callback function runs when you call the Invoke() method on the BindableFunction. The callback function arguments that you pass to Invoke(). The code invoking the function yields until the function halts or returns a value.

Creating Custom Synchronous Events

To create a new BindableFunction in the Explorer:

  1. Hover over the container in the Explorer into which you want to insert a BindableFunction. It's common to put BindableFunction objects in a Functions folder in ServerScriptService to use them with Scripts and ReplicatedStorage to use them with LocalScripts.
  2. Click the button that appears to the right of the container to open the Insert Object menu.
  3. Select BindableFunction.
  4. Rename the function to describe its purpose using PascalCase.

To create a new BindableFunction in a Script:


-- Script in ServerScriptService
local getHelloWorld = Instance.new("BindableFunction")
getHelloWorld.Name = GetHelloWorld
getHelloWorld.Parent = ServerScriptService

Using Custom Synchronous Events

Each BindableFunction has only one OnInvoke callback. If you have multiple definitions, then only the one defined latest runs. If the OnInvoke callback does not have a return statement, it returns nil.

To define the OnInvoke callback of a BindableFunction in ServerScriptService named GetHelloWorld:


-- Script in ServerScriptService named DataManager
local ServerScriptService = game:GetService("ServerScriptService")
local getHelloWorld = ServerScriptService.GetHelloWorld
local HELLO_WORLD = "Hello world!"
getHelloWorld.OnInvoke = function()
return HELLO_WORLD
end

To invoke a BindableFunction in ServerScriptService named GetHelloWorld:


-- Script in ServerScriptService named DataReceiver
local ServerScriptService = game:GetService("ServerScriptService")
local getHelloWorld = ServerScriptService.GetHelloWorld
print(getHelloWorld:Invoke())
print("Hello world again!")

Notice that the "Hello world!" prints before "Hello world again!" because the Script yields until the BindableFunction halts or returns a value.

Networking Events

All experiences require communication between the server and the players' clients. For example when a player presses W to move forward, the keyboard input is received on the player's client. The client then communicates this to the server, which responds by moving the position of the player's character forward. RemoteEvent and RemoteFunction objects allow you to create your own events and functions to communicate your own behavior between the client-server boundary.

RemoteEvents allow for asynchronous, one-way communication across the boundary, while RemoteFunctions allow for synchronous, two-way communication (sending a request across the boundary and yielding until a response is received from the recipient).

The following table serves as a quick reference for how to use remote events and functions to communicate between the client and server.

Remote Events

Client → Server
ClientRemoteEvent:FireServer(args)
ServerRemoteEvent.OnServerEvent:Connect(function(player, args))
Server → Client
ServerRemoteEvent:FireClient(player, args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))
Server → All Clients
ServerRemoteEvent:FireAllClients(args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

Remote Functions

Client → Server → Client
ClientserverResponse = RemoteFunction:InvokeServer(args)
ServerRemoteFunction.OnServerInvoke = function(player, args)
Server → Client → Server  
Serious Risks

Asynchronous Networking Events

A RemoteEvent object allows asynchronous, one-way communication across the client-server boundary. You can use it to send requests across the boundary without yielding for a response.

Creating Asynchronous Networking Events

Create RemoteEvent objects in a location that both the client and server can access, such as ReplicatedStorage.

  1. In the Explorer window, hover over ReplicatedStorage and click the button.

  2. Click RemoteEvent from the contextual menu.

  3. Rename the new RemoteEvent to describe its purpose.

Using Asynchronous Networking Events

You can use RemoteEvents to facilitate one-way communication from one client to the server, the server to one client, or the server to all clients.

Client to Server

You can use a LocalScript to trigger an event on the server by calling the FireServer() method on a RemoteEvent. If you pass arguments to FireServer(), they pass to the event handler on the server. The first parameter of the event handler is always the Player object of the client that calls it, and additional parameters follow.

ClientRemoteEvent:FireServer(args)
ServerRemoteEvent.OnServerEvent:Connect(function(player, args))

The following LocalScript and Script fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The LocalScript calls FireServer() on the RemoteEvent instance. In response, the Script connects an event handler to OnServerEvent that creates a new Part on the server.

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
-- Fire the RemoteEvent and pass additional arguments
remoteEvent:FireServer(BrickColor.Red(), Vector3.new(0, 25, 0))
Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
local function onCreatePart(player, partColor, partPosition)
print(player.Name .. " fired the RemoteEvent")
local newPart = Instance.new("Part")
newPart.BrickColor = partColor
newPart.Position = partPosition
newPart.Parent = workspace
end
remoteEvent.OnServerEvent:Connect(onCreatePart)
Server to Client

You can use a Script to trigger an event on a client by calling the FireClient() method on a RemoteEvent. The first argument of the FireClient() is the Player object of the client that you want to respond to the event, and additional arguments pass to the client.

ServerRemoteEvent:FireClient(player, args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

The following Script and LocalScript fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The Script calls FireClient() on the RemoteEvent to fire it. The LocalScript connects an event handler to the OnClientEvent event. The event handler doesn't need to list the Player object as its first argument because you can determine the player on the client with Players.LocalPlayer.

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
local function onPlayerAdded(player)
print("[Server] Firing event to player", player.Name)
remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)
LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
local player = Players.LocalPlayer
local function onNotifyPlayer(maxPlayers, respawnTime)
print("[Client] Event received by player", player.Name)
print(maxPlayers, respawnTime)
end
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)
Server to All Clients

You can use a Script to trigger an event on all clients by calling the FireAllClients() method on a RemoteEvent. Unlike FireClient(), the FireAllClients() method doesn't require a Player object because it fires the RemoteEvent to all clients.

ServerRemoteEvent:FireAllClients(args)
ClientRemoteEvent.OnClientEvent:Connect(function(args))

The following Script and LocalScript fire and respond to a RemoteEvent instance in ReplicatedStorage named RemoteEventTest. The Script calls FireAllClients() on the RemoteEvent to fire it. The LocalScript connects an event handler to the OnClientEvent event that prints to the Output window.

Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
local secondsRemaining = 5
-- Fire the RemoteEvent every second until time expires
for t = secondsRemaining, 1, -1 do
remoteEvent:FireAllClients(t)
task.wait(1)
end
LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
local function onTimerUpdate(seconds)
print(seconds)
end
-- Call "onTimerUpdate()" when the server fires the RemoteEvent
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
Client to Client

Clients cannot communicate directly with other clients, although you can effectively dispatch an event from one client to another by using the FireServer() method, then calling FireClient() or FireAllClients() in the event handler for OnServerEvent.

Synchronous Networking Events

RemoteFunction allows synchronous, two-way communication across the client-server boundary. The sender of a remote function will wait to receive a response from the recipient.

Creating Synchronous Networking Events

Create RemoteFunction objects in a location that both the client and server can access, such as ReplicatedStorage.

  1. In the Explorer window, hover over ReplicatedStorage and click the button.

  2. Click RemoteFunction from the contextual menu.

  3. Rename the new RemoteFunction to describe its purpose.

Using Synchronous Networking Events

You can use RemoteFunction objects to facilitate two-way communication between one client and the server or the server and one client.

Client to Server to Client

You can use a Script to call a function on the server by calling the InvokeServer() method on a RemoteFunction. The LocalScript that invokes the RemoteFunction yields until the callback of the RemoteFunction returns. Arguments that you pass to InvokeServer() pass to the OnServerInvoke callback of the RemoteFunction. If you define multiple callbacks to the same RemoteFunction, the last definition executes.

ClientRemoteFunction:InvokeServer(args)
ServerRemoteFunction.OnServerInvoke = function(player, args)

The following LocalScript and Script have two-way communication using a RemoteFunction instance in ReplicatedStorage named RemoteFunctionTest. The LocalScript calls InvokeServer() on the RemoteFunction instance with extra arguments. In response, the Script defines the callback of the RemoteFunction and calls it using the Player of the client and the extra arguments. The Script then returns the return value of the callback to the LocalScript.

LocalScript in StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
-- Invoke the Function
-- Pass a brick color and position when invoking the function
local newPart = remoteFunction:InvokeServer(BrickColor.Red(), Vector3.new(0, 25, 0))
print("The server created the requested part:", newPart)
Script in ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
-- Script in ServerScriptService to create a Part with the passed properties
local function createPart(player, partColor, partPosition)
print(player.Name .. " requested a new part")
local newPart = Instance.new("Part")
-- Use partColor and partPosition to set the part's BrickColor and Position
newPart.BrickColor = partColor
newPart.Position = partPosition
newPart.Parent = workspace
return newPart
end
-- Bind createPart() to the remote function's OnServerInvoke callback
remoteFunction.OnServerInvoke = createPart
Server to Client to Server

You can use a Script to call a function on the client by calling the InvokeClient() method on a RemoteFunction, but it has the following serious risks:

For actions that don't require two-way communications, such as updating a GUI, use a RemoteEvent and communicate from the server to client.

Argument Limitations

When an event is fired or invoked, it forwards any arguments that you pass with it or to the callback function. For built-in events, follow the argument limitations on the API reference. For custom events and networking events, though the arguments can be all types of Roblox objects and Luau date types such as numbers, strings, and booleans, there are certain limitations that apply to the arguments.

Non-String Indices

If any indices of a passed table are non-string types such as an Instance, userdata, or function, Roblox automatically converts those indices to strings.


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local bindableEvent = ReplicatedStorage:WaitForChild("BindableEvent")
local function onEventFire(passedTable)
for k, v in passedTable do
print(typeof(k)) --> string
end
end
bindableEvent.Event:Connect(onEventFire)
-- Fire event with a table containing a workspace instance as a key
bindableEvent:Fire({
[workspace.Baseplate] = true
})

Functions in Remotes

Functions referenced as arguments in a RemoteEvent or RemoteFunction will not be replicated across the client-server boundary, making it impossible to pass functions remotely. Instead, the resulting argument on the receiving side will be nil.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local function testFunction()
print("Hello world!")
end
-- Fire remote event with function as an argument
remoteEvent:FireAllClients(testFunction)
LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local function onClientEvent(func)
print(func) --> nil
end
remoteEvent.OnClientEvent:Connect(onClientEvent)

Mixed Tables

If you pass a table with a mix of numeric and string keys, the values indexed by string keys are lost. In the following code sample, colorData is a mixed table. When the server receives colorData, it receives only indices [1] and [2] containing "Blue" and "Yellow". The remaining data is lost.

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Mixed table
local colorData = {}
colorData[1] = "Blue"
colorData[2] = "Yellow"
colorData["Color1"] = "Green"
colorData["Color2"] = "Red"
-- Table with two key-indexed sub-tables
local playerData = {}
playerData["CharData"] = {
-- All children indexed by key
CharName = "Diva Dragonslayer",
CharClass = "Knight"
}
playerData["Inventory"] = {
-- All children numerically indexed
"Sword",
"Bow",
"Rope"
}
remoteEvent:FireServer(colorData)
remoteEvent:FireServer(playerData)

Table Identities

Tables passed as arguments to custom events are copied, meaning they will not be exactly equivalent to those provided when firing an asynchronous event or invoking a synchronous event. Tables returned to the invoker will also be copied and not equivalent to those provided. You can demonstrate this by running the following script on a BindableFunction and observing how the table identities differ.


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local bindableFunction = ReplicatedStorage:WaitForChild("BindableFunction")
bindableFunction.OnInvoke = function(passedTable)
print(tostring(passedTable))
return passedTable
end
local testTable = {message = "Hello world!"}
print(tostring(testTable))
local invokeReturn = bindableFunction:Invoke(testTable)
print(tostring(invokeReturn))
Original Identitytable: 0xf775b8bcc5e44cce
Identity on Invocationtable: 0x03b4045c4f99ec0f
Identity on Returntable: 0x11f690dfe56daf6e

Empty Indices

If a table has numeric indices, and one of the values is nil, that index and its subsequent indices are lost. In the following code example, the message table has [3] = nil, so that index and [4] = "Goodbye world!" are lost in the transmission.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local message = {
[1] = "Hello",
[2] = "world!",
[3] = nil,
[4] = "Goodbye world!"
}
remoteEvent:FireAllClients(message)
LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local function onClientEvent(param)
print(param) --> [1] = "Hello", [2] = "world!"
end
remoteEvent.OnClientEvent:Connect(onClientEvent)

Metatables

If a table has a metatable, all of the metatable information is lost in the transfer.

In the following code sample, the NumWheels property is part of the Car metatable. When the server receives the following table, the truck table has the Name property but not the NumWheels property.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local Car = {}
Car.NumWheels = 4
Car.__index = Car
local truck = {}
truck.Name = "MyTruck"
setmetatable(truck, Car)
remoteEvent:FireAllClients(truck)
LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
local function onClientEvent(param)
print(param) --> {["Name"] = "MyTruck"}
end
remoteEvent.OnClientEvent:Connect(onClientEvent)

Non-Replicated Instances

If an event or function passes a value that's only visible to the sender, Roblox doesn't replicate it across the client-server boundary and passes nil instead of the value. For example, if a Script tries to pass a descendant of ServerStorage, the client listening to the event receives a nil value because that object isn't replicable for the client.

Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Will be received as "nil" because client can't access ServerStorage
local storedPart = Instance.new("Part")
storedPart.Parent = ServerStorage
local function onPlayerAdded(player)
remoteEvent:FireClient(player, storedPart)
end
Players.PlayerAdded:Connect(onPlayerAdded)

Similarly, if you create a part in a LocalScript and try to pass it to a Script, the server sees nil because the part isn't replicable for the server.

LocalScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Will be received as "nil" because the server doesn't know about this part
local clientPart = Instance.new("Part")
clientPart.Parent = workspace
remoteEvent:FireServer(clientPart)