Remote network events and callbacks allow for back-and-forth communication across the client-server boundary.
Roblox games are multiplayer by default, so all games inherently communicate between the server and the players' connected clients. In the simplest case, as players move their characters, certain Humanoid properties, such as states, are communicated to the server, which passes this information to other connected clients.
Remote events and callbacks let you communicate across the client-server boundary:
RemoteEvents enable one-way communication (sending a request and not yielding for a response).
UnreliableRemoteEvents enable one-way communication for data that changes continuously or isn't critical to the game's state. These events trade ordering and reliability for improved network performance.
RemoteFunctions enable two-way communication (sending a request and yielding until a response is received from the recipient).
Unlike bindable events, which have more limited utility, the use cases for remote events and functions are too numerous to list:
Gameplay - Basic gameplay, such as a player reaching the end of a level, can require a remote event. A client script notifies the server, and server scripts reset the player's position.
Server verification - If a player tries to drink a potion, do they actually have that potion? To ensure fairness, the server has to be the source of truth for a game. A client script can use a remote event to notify the server that the player is drinking a potion, and then server scripts can decide whether the player actually has that potion and whether to confer any benefits.
User interface updates - As the game's state changes, server scripts can use remote events to notify clients of changes to scores, objectives, etc.
In-game Marketplace purchases - For an example implementation that uses remote functions, see Prompt subscription purchases.
Hover over the container into which you want to insert the RemoteEvent. In order to ensure both server and client access, it must be in a place where both sides can see it, such as ReplicatedStorage, although in some cases it's appropriate to store it in Workspace or inside a Tool.
Click the ⊕ button that appears to the right of the container's name and insert a RemoteEvent instance.
Clients cannot communicate directly with other clients, although you can effectively dispatch an event from one client to another by using the RemoteEvent:FireServer() method, then calling FireClient() or FireAllClients() in the event handler for OnServerEvent.
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 with certain limitations. Note that the first parameter of the event handler on the server is always the Player object of the client that calls it, and additional parameters follow.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononCreatePart(player, partColor, partPosition)
print(player.Name .. " fired the RemoteEvent")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
end-- Connect function to event
remoteEvent.OnServerEvent:Connect(onCreatePart)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Fire the remote event and pass additional arguments
remoteEvent:FireServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
You can use a Script to trigger an event on a client by calling the FireClient() method on a RemoteEvent. The first argument for FireClient() is the Player object of the client that you want to respond to the event, and additional arguments pass to the client with certain limitations. Note that the event handler doesn't need to include the Player object as its first argument because you can determine the player on the client with Players.LocalPlayer.
Server
RemoteEvent:FireClient(player, args)
Client
RemoteEvent.OnClientEvent:Connect(function(args))
The following LocalScript connects an event handler to the OnClientEvent event. The accompanying Script then listens for incoming players to the server and calls FireClient() for each with arbitrary data.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local player = Players.LocalPlayer
localfunctiononNotifyPlayer(maxPlayers, respawnTime)
print("[Client] Event received by player", player.Name)
print(maxPlayers, respawnTime)
end-- Connect function to event
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Listen for incoming players and dispatch remote event to eachlocalfunctiononPlayerAdded(player)
print("[Server] Firing event to player", player.Name)
remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)
The following LocalScript connects an event handler to the OnClientEvent event which outputs a remaining countdown time. The accompanying Script then calls FireAllClients() in a loop every second to fire the RemoteEvent for all clients.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononTimerUpdate(seconds)
print(seconds)
end-- Connect function to event
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote event instancelocal remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local countdown = 5-- Fire the RemoteEvent every second until time expiresfor timeRemaining = -1, countdown do
remoteEvent:FireAllClients(countdown - timeRemaining)
task.wait(1)
end
A RemoteFunction object facilitates synchronous, two-way communication across the client-server boundary. The sender of a remote function will yield until it receives a response from the recipient.
Hover over the container into which you want to insert the RemoteFunction. In order to ensure both server and client access, it must be in a place where both sides can see it, such as ReplicatedStorage, although in some cases it's appropriate to store it in Workspace or inside a Tool.
Click the ⊕ button that appears to the right of the container's name and insert a RemoteFunction instance.
The following Script defines the callback function via OnServerInvoke and returns the requested Part through its return value. The accompanying LocalScript then calls InvokeServer() with extra arguments defining the requested part color and position.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
-- Get reference to remote function instancelocal remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Callback functionlocalfunctioncreatePart(player, partColor, partPosition)
print(player.Name .. " requested a new part")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
return newPart
end-- Set function as remote function's callback
remoteFunction.OnServerInvoke = createPart
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get reference to remote function instancelocal remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Pass a color and position when invoking the callbacklocal newPart = remoteFunction:InvokeServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
-- Output the returned part referenceprint("The server created the requested part:", newPart)
Each remote type makes different promises about whether a message arrives and in what order. These promises only apply to a single connection in a single direction, meaning from the server to one specific client, or from one specific client to the server.
If a message is lost in transit, Roblox retransmits it until it arrives, as long as the recipient stays connected.
Messages arrive in the order you fire them, including across different RemoteEvent instances. For example, if the server fires event A, then B, then A again to the same client, that client receives A, B, A. This applies whether you use FireClient() or FireAllClients().
If a client fires events faster than the throttling limit, the server processes the excess events later instead of dropping them, and they keep their order.
A reliable, ordered message can still fail to reach your code in the following cases:
No handler is connected. If nothing is connected to OnServerEvent or OnClientEvent when a message arrives, Roblox queues it and delivers it, in order, as soon as a handler connects. The queue is limited in both count and memory. Once it's full, Roblox discards further messages and logs a Remote event invocation error to the Output window.
The recipient disconnects. Messages that haven't been delivered when a client leaves are lost.
Ordering applies to when each handler starts, not when it finishes. If a handler yields, such as by calling task.wait() or WaitForChild(), Roblox can start handling the next message before the earlier handler resumes.
RemoteFunctions send invocations and responses reliably, so Roblox retransmits lost messages as long as both sides stay connected. Because InvokeServer() and InvokeClient() yield until a response arrives, a single thread can't have more than one invocation in flight. Separate threads can invoke at the same time, though, and if a callback yields, responses can come back in a different order than the invocations were sent.
If no callback is assigned to OnServerInvoke or OnClientInvoke, Roblox queues invocations until one is. This queue has the same kind of limits as the RemoteEvent queue.
Roblox doesn't guarantee the relative order of RemoteFunction invocations and RemoteEvent messages. They often arrive in the order you send them, but they're queued separately while no handler is connected, and a yielding callback can delay a response. If your code depends on strict ordering, send all related messages through the same remote type or include your own sequence number.
UnreliableRemoteEvents guarantee neither delivery nor order. They also have no ordering relationship with RemoteEvents or RemoteFunctions. Roblox might drop a message in any of the following cases, without resending it:
The message is lost in transit.
The network is congested and the message waits too long to send.
The payload is larger than 1,000 bytes.
The client fires faster than the throttling limit. Unlike a RemoteEvent, the server drops the excess messages instead of delaying them.
Nothing is connected to OnServerEvent or OnClientEvent when the message arrives. Unlike a RemoteEvent, Roblox discards the message immediately instead of queuing it.
Roblox also doesn't wait for an earlier message to arrive before processing a later one, so messages can arrive out of order. If order matters, include a timestamp or sequence number in each message and ignore messages older than the latest one you've handled.
Client-to-server messages are subject to a rate limit that each remote type shares across all of its instances. For current limits, see the Throttling section of RemoteEvent and the payload limits in UnreliableRemoteEvent.
When you fire a RemoteEvent or invoke a RemoteFunction, it forwards any arguments that you pass with the event or to the callback function. Any type of Roblox object such as an Enum, Instance, or others can be passed, as well as Luau types such as numbers, strings, and booleans, although you should carefully explore the following limitations.
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.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononEventFire(passedTable)
for k, v in passedTable doprint(typeof(k)) --> stringendend-- Connect function to event
remoteEvent.OnClientEvent:Connect(onEventFire)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Listen for incoming players and dispatch remote event to eachlocalfunctiononPlayerAdded(player)
remoteEvent:FireClient(player,
{
[Workspace.Baseplate] = true
}
)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Functions included as arguments for 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.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononClientEvent(func)
print(func) --> nilend
remoteEvent.OnClientEvent:Connect(onClientEvent)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiontestFunction()
print("Hello world!")
end-- Fire remote event with function as an argument
remoteEvent:FireAllClients(testFunction)
If you pass a table of data, do not pass a mixed table of numeric and string keys. Instead, pass a table that consists entirely of key-value pairs (dictionary) or entirely of numeric indices.
Whether passing a dictionary table or a numerically indexed table, avoid nil values for any index.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononEventFire(player, passedTable)
for k, v in passedTable doprint(k .. " = " .. v)
--> 1 = Sword--> 2 = Bow--> CharName = Diva Dragonslayer--> CharClass = Rogueendend-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEventFire)
Tables passed as arguments to remote events/callbacks are copied, meaning they will not be exactly equivalent to those provided when firing the event or invoking the callback. Nor will tables returned to the invoker be exactly equivalent to those provided. You can demonstrate this by running the following script on a RemoteFunction and observing how the table identities differ.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
-- Callback functionlocalfunctionreturnTable(player, passedTable)
-- Output table identity on invocationprint(tostring(passedTable)) --> table: 0x48eb7aead27563d9return passedTable
end-- Set function as remote function's callback
remoteFunction.OnServerInvoke = returnTable
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")
local inventoryData = {
"Sword", "Bow"
}
-- Output original table identityprint(tostring(inventoryData)) --> table: 0x059bcdbb2b576549local invokeReturn = remoteFunction:InvokeServer(inventoryData)
-- Output table identity upon returnprint(tostring(invokeReturn)) --> table: 0x9fcae7919563a0e9
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.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
localfunctiononEvent(player, param)
print(param) --> {["Name"] = "MyTruck"}end-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEvent)
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local Car = {}
Car.NumWheels = 4
Car.__index = Car
local truck = {}
truck.Name = "MyTruck"setmetatable(truck, Car)
-- Fire event with table including a metatable
remoteEvent:FireServer(truck)
If a RemoteEvent or RemoteFunction 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 passes a descendant of ServerStorage, the client listening to the event will receive a nil value because that object isn't replicable for the client.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Will be received as "nil" because client can't access ServerStoragelocal storedPart = Instance.new("Part")
storedPart.Parent = ServerStorage
localfunctiononPlayerAdded(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 will see nil because the part isn't replicable for the server.
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
-- Will be received as "nil" because the server doesn't know about this partlocal clientPart = Instance.new("Part")
clientPart.Parent = Workspace
remoteEvent:FireServer(clientPart)