---
title: "Remote events and callbacks"
url: /docs/en-us/scripting/events/remote
last_updated: 2026-04-24T21:49:21Z
description: "Remote network events and callbacks allow for back-and-forth communication across the client-server boundary."
---

# Remote events and callbacks

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 `Class.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:

- `Class.RemoteEvent|RemoteEvents` enable one-way communication (sending a request and **not** yielding for a response).
- `Class.UnreliableRemoteEvent|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.
- `Class.RemoteFunction|RemoteFunctions` enable two-way communication (sending a request and yielding until a response is received from the recipient).

Unlike [bindable events](/docs/en-us/scripting/events/bindable.md), 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](/docs/en-us/production/monetization/subscriptions.md#prompt-subscription-purchases).

## Quick reference

The following tables serve as a quick reference for how to use `Class.RemoteEvent|RemoteEvents` and `Class.RemoteFunction|RemoteFunctions` to communicate between the client and server.

#### Remote events

| [Client → Server](#client-server) |
| --- |
| Client | `RemoteEvent:FireServer(args)` |
| Server | `RemoteEvent.OnServerEvent:Connect(function(player, args))` |
| [Server → Client](#server-client) |
| Server | `RemoteEvent:FireClient(player, args)` |
| Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |
| [Server → All Clients](#server-all-clients) |
| Server | `RemoteEvent:FireAllClients(args)` |
| Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |

#### Remote functions

| [Client → Server → Client](#client-server-client) |
| --- |
| Client | `serverResponse = RemoteFunction:InvokeServer(args)` |
| Server | `RemoteFunction.OnServerInvoke = function(player, args)` |
| [Server → Client → Server](#server-client-server) |
| See [risks](#server-client-server). |

## Remote events

A `Class.RemoteEvent` object facilitates asynchronous, one-way communication across the client-server boundary without yielding for a response.

To create a new `Class.RemoteEvent` via the [Explorer](/docs/en-us/studio/explorer.md) window in Studio:

1. Hover over the container into which you want to insert the `Class.RemoteEvent`. In order to ensure both server and client access, it must be in a place where both sides can see it, such as `Class.ReplicatedStorage`, although in some cases it's appropriate to store it in `Class.Workspace` or inside a `Class.Tool`.
2. Click the **⊕** button that appears to the right of the container's name and insert a **RemoteEvent** instance.
3. Rename the instance to describe its purpose.

Once you've created a `Class.RemoteEvent`, it can facilitate one-way communication from [client to server](#client-server), from [server to client](#server-client), or from the [server to all clients](#server-all-clients).

_Client → Server_

_Server → Client_

_Server → All Clients_

> **Info:** Clients cannot communicate directly with other clients, although you can effectively dispatch an event from one client to another by using the `Class.RemoteEvent:FireServer()` method, then calling `Class.RemoteEvent:FireClient()|FireClient()` or `Class.RemoteEvent:FireAllClients()|FireAllClients()` in the event handler for `Class.RemoteEvent.OnServerEvent|OnServerEvent`.
### Client → server

You can use a `Class.LocalScript` to trigger an event on the [server](/docs/en-us/projects/client-server.md) by calling the `Class.RemoteEvent:FireServer()|FireServer()` method on a `Class.RemoteEvent`. If you pass arguments to `Class.RemoteEvent:FireServer()|FireServer()`, they pass to the event handler on the server with certain [limitations](#argument-limitations). Note that the first parameter of the event handler on the server is always the `Class.Player` object of the client that calls it, and additional parameters follow.

| Client | `RemoteEvent:FireServer(args)` |
| --- | --- |
| Server | `RemoteEvent.OnServerEvent:Connect(function(player, args))` |

The following `Class.Script` connects an event handler to `Class.RemoteEvent.OnServerEvent|OnServerEvent` that creates a new `Class.Part` on the server. The accompanying `Class.LocalScript` then calls `Class.RemoteEvent:FireServer()|FireServer()` on the `Class.RemoteEvent` instance with the desired `Class.BasePart.Color|Color` and `Class.BasePart.Position|Position` for the part.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onCreatePart(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)
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

-- Fire the remote event and pass additional arguments
remoteEvent:FireServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))
```

### Server → client

You can use a `Class.Script` to trigger an event on a [client](/docs/en-us/projects/client-server.md) by calling the `Class.RemoteEvent:FireClient()|FireClient()` method on a `Class.RemoteEvent`. The first argument for `Class.RemoteEvent:FireClient()|FireClient()` is the `Class.Player` object of the client that you want to respond to the event, and additional arguments pass to the client with certain [limitations](#argument-limitations). Note that the event handler doesn't need to include the `Class.Player` object as its first argument because you can determine the player on the client with `Class.Players.LocalPlayer`.

| Server | `RemoteEvent:FireClient(player, args)` |
| --- | --- |
| Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |

The following `Class.LocalScript` connects an event handler to the `Class.RemoteEvent.OnClientEvent|OnClientEvent` event. The accompanying `Class.Script` then listens for incoming players to the server and calls `Class.RemoteEvent:FireClient()|FireClient()` for each with arbitrary data.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local player = Players.LocalPlayer

local function onNotifyPlayer(maxPlayers, respawnTime)
   print("[Client] Event received by player", player.Name)
   print(maxPlayers, respawnTime)
end

-- Connect function to event
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

-- Listen for incoming players and dispatch remote event to each
local function onPlayerAdded(player)
   print("[Server] Firing event to player", player.Name)
   remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end
Players.PlayerAdded:Connect(onPlayerAdded)
```

### Server → all clients

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

| Server | `RemoteEvent:FireAllClients(args)` |
| --- | --- |
| Client | `RemoteEvent.OnClientEvent:Connect(function(args))` |

The following `Class.LocalScript` connects an event handler to the `Class.RemoteEvent.OnClientEvent|OnClientEvent` event which outputs a remaining countdown time. The accompanying `Class.Script` then calls `Class.RemoteEvent:FireAllClients()|FireAllClients()` in a loop every second to fire the `Class.RemoteEvent` for all clients.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onTimerUpdate(seconds)
	print(seconds)
end

-- Connect function to event
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get reference to remote event instance
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local countdown = 5

-- Fire the RemoteEvent every second until time expires
for timeRemaining = -1, countdown do
	remoteEvent:FireAllClients(countdown - timeRemaining)
	task.wait(1)
end
```

## Remote callbacks

A `Class.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.

To create a new `Class.RemoteFunction` via the [Explorer](/docs/en-us/studio/explorer.md) window in Studio:

1. Hover over the container into which you want to insert the `Class.RemoteFunction`. In order to ensure both server and client access, it must be in a place where both sides can see it, such as `Class.ReplicatedStorage`, although in some cases it's appropriate to store it in `Class.Workspace` or inside a `Class.Tool`.
2. Click the **⊕** button that appears to the right of the container's name and insert a **RemoteFunction** instance.
3. Rename the instance to describe its purpose.

Once you've created a `Class.RemoteFunction`, it can facilitate two-way communication between [client and server](#client-server-client) or between [server and client](#server-client-server).

_Client → Server → Client_

_Server → Client → Server_

### Client → server → client

You can use a `Class.LocalScript` to call a function on the [server](/docs/en-us/projects/client-server.md) by calling the `Class.RemoteFunction:InvokeServer()|InvokeServer()` method on a `Class.RemoteFunction`. Unlike a [remote event](#remote-events), the `Class.LocalScript` that invokes the `Class.RemoteFunction` yields until the callback returns. Arguments that you pass to `Class.RemoteFunction:InvokeServer()|InvokeServer()` pass to the `Class.RemoteFunction.OnServerInvoke|OnServerInvoke` callback of the `Class.RemoteFunction` with certain [limitations](#argument-limitations). Note that if you define multiple callbacks to the same `Class.RemoteFunction`, only the last definition executes.

| Client | `RemoteFunction:InvokeServer(args)` |
| --- | --- |
| Server | `RemoteFunction.OnServerInvoke = function(player, args)` |

The following `Class.Script` defines the callback function via `Class.RemoteFunction.OnServerInvoke|OnServerInvoke` and returns the requested `Class.Part` through its `return` value. The accompanying `Class.LocalScript` then calls `Class.RemoteFunction:InvokeServer()|InvokeServer()` with extra arguments defining the requested part color and position.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

-- Get reference to remote function instance
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")

-- Callback function
local function createPart(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
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get reference to remote function instance
local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")

-- Pass a color and position when invoking the callback
local newPart = remoteFunction:InvokeServer(Color3.fromRGB(255, 0, 0), Vector3.new(0, 25, -20))

-- Output the returned part reference
print("The server created the requested part:", newPart)
```

### Server → client → server

You can use a `Class.Script` to call a function on the client by calling the `Class.RemoteFunction:InvokeClient()|InvokeClient()` method on a `Class.RemoteFunction`, but it has serious risks as follows:

- If the client throws an error, the server throws the error too.
- If the client disconnects while it's being invoked, `Class.RemoteFunction:InvokeClient()|InvokeClient()` throws an error.
- If the client doesn't return a value, the server yields forever.

For actions that don't require two-way communications, such as updating a GUI, use a `Class.RemoteEvent` and communicate from [server to client](#server-client).

## Argument limitations

When you fire a `Class.RemoteEvent` or invoke a `Class.RemoteFunction`, it forwards any arguments that you pass with the event or to the callback function. Any type of Roblox object such as an `Datatype.Enum`, `Class.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.

### Non-string indices

If any **indices** of a passed table are non-string types such as an `Class.Instance`, [userdata](/docs/en-us/luau/userdata.md), or [function](/docs/en-us/luau/functions.md), Roblox automatically converts those indices to strings.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onEventFire(passedTable)
	for k, v in passedTable do
		print(typeof(k))  --> string
	end
end

-- Connect function to event
remoteEvent.OnClientEvent:Connect(onEventFire)
```

```lua
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 each
local function onPlayerAdded(player)
	remoteEvent:FireClient(player,
		{
			[Workspace.Baseplate] = true
		}
	)
end
Players.PlayerAdded:Connect(onPlayerAdded)
```

### Passed functions

Functions included as arguments for a `Class.RemoteEvent` or `Class.RemoteFunction` will **not** be replicated across the [client-server](/docs/en-us/projects/client-server.md) boundary, making it impossible to pass functions remotely. Instead, the resulting argument on the receiving side will be `nil`.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onClientEvent(func)
	print(func)  --> nil
end

remoteEvent.OnClientEvent:Connect(onClientEvent)
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function testFunction()
	print("Hello world!")
end

-- Fire remote event with function as an argument
remoteEvent:FireAllClients(testFunction)
```

### Table indexing

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.

> **Warning:** Whether passing a dictionary table **or** a numerically indexed table, avoid `nil` values for any index.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onEventFire(player, passedTable)
	for k, v in passedTable do
		print(k .. " = " .. v)
		--> 1 = Sword
		--> 2 = Bow
		--> CharName = Diva Dragonslayer
		--> CharClass = Rogue
	end
end

-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEventFire)
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

-- Numerically indexed table
local inventoryData = {
	"Sword", "Bow"
}
-- Dictionary table
local characterData = {
	CharName = "Diva Dragonslayer",
	CharClass = "Rogue"
}

remoteEvent:FireServer(inventoryData)
remoteEvent:FireServer(characterData)
```

### Table identities

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 `Class.RemoteFunction` and observing how the table identities differ.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")

-- Callback function
local function returnTable(player, passedTable)
	-- Output table identity on invocation
	print(tostring(passedTable))  --> table: 0x48eb7aead27563d9
	return passedTable
end

-- Set function as remote function's callback
remoteFunction.OnServerInvoke = returnTable
```

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteFunction = ReplicatedStorage:FindFirstChildOfClass("RemoteFunction")

local inventoryData = {
	"Sword", "Bow"
}
-- Output original table identity
print(tostring(inventoryData))  --> table: 0x059bcdbb2b576549

local invokeReturn = remoteFunction:InvokeServer(inventoryData)

-- Output table identity upon return
print(tostring(invokeReturn))  --> table: 0x9fcae7919563a0e9
```

### 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.

```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")

local function onEvent(player, param)
	print(param)  --> {["Name"] = "MyTruck"}
end

-- Connect function to event
remoteEvent.OnServerEvent:Connect(onEvent)
```

```lua
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)
```

### Non-replicated instances

If a `Class.RemoteEvent` or `Class.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 `Class.Script` passes a descendant of `Class.ServerStorage`, the client listening to the event will receive a `nil` value because that object isn't replicable for the client.

```lua
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 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 `Class.LocalScript` and try to pass it to a `Class.Script`, the server will see `nil` because the part isn't replicable for the server.

```lua
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 part
local clientPart = Instance.new("Part")
clientPart.Parent = Workspace

remoteEvent:FireServer(clientPart)
```