Networking
Rivet networking is generated from Client surfaces. You describe the public network boundary in the Unit, and Rivet creates the RemoteFunctions, RemoteEvents, server dispatchers, client proxies, signal helpers, contract checks, codec transforms, debug hooks, and metadata folders needed to make that surface callable.
The important rule is simple: only declared Client surfaces are reachable from the client.
Inventory.Surfaces = {
Client = {
GetItems = "Query",
EquipItem = "Action",
ItemAdded = "Signal",
},
}
If Inventory has a private DeleteEverything method but does not declare it under Surfaces.Client, Rivet does not create a remote for it.
Remote Tree
On the server, Rivet creates remotes under ReplicatedStorage.RivetRemotes.
ReplicatedStorage
RivetRemotes
Inventory
GetItems RemoteFunction
EquipItem RemoteEvent
ItemAdded RemoteEvent
Each remote also gets metadata:
__RivetKindstring value withQuery,Action, orSignal__RivetContractsfolder when contracts existArgscontract list for Query and Action argsReturnsstring value for Query return contractsPayloadcontract list for Signal payloads
Client proxies read this metadata to know how to encode arguments, decode results, and expose the right method shape.
Query
A Query is request/response. It uses a RemoteFunction.
Inventory.Surfaces = {
Client = {
GetItems = {
Kind = "Query",
Returns = "table",
},
},
}
function Inventory:GetItems(player: Player)
return self.Data:GetProfile(player).Items
end
Client code:
local Inventory = Rivet:Get("Inventory")
local items = Inventory:GetItems()
Server dispatch flow:
- Client proxy encodes arguments if custom codecs are declared.
- Proxy calls
RemoteFunction:InvokeServer. - Server dispatcher receives
playerand packed args. - Server decodes custom args.
- Server validates
Argscontracts. - Server calls
Inventory:GetItems(player, ...). - Server validates the
Returnscontract. - Server encodes custom return values.
- Client proxy decodes custom return values and returns the result.
Queries use RemoteFunction, so the client waits for the server result. Keep Query methods bounded and avoid using them for long-running work. For long work, use an Action to request work and a Signal to announce progress or completion.
Action
An Action is client-to-server work without a return value. It uses a RemoteEvent.
Inventory.Surfaces = {
Client = {
EquipItem = {
Kind = "Action",
Args = { "string" },
},
},
}
function Inventory:EquipItem(player: Player, itemId: string)
print(player.Name, "equipped", itemId)
end
Client code:
local Inventory = Rivet:Get("Inventory")
Inventory:EquipItem("Sword")
Server dispatch flow:
- Client proxy encodes custom args.
- Proxy calls
RemoteEvent:FireServer. - Server dispatcher receives
playerand packed args. - Server decodes custom args.
- Server validates
Argscontracts. - Server calls
Inventory:EquipItem(player, ...).
Actions are a good default for player input. The server can choose whether to send a later Signal, update replicated state, or silently reject the request.
Signal
A Signal is server-to-client. It uses a RemoteEvent, but Rivet gives the server a helper under self.Client.
Inventory.Surfaces = {
Client = {
ItemAdded = {
Kind = "Signal",
Payload = { "string" },
},
},
}
function Inventory:AddItem(player: Player, itemId: string)
self.Client.ItemAdded:Fire(player, itemId)
end
Server helpers:
self.Client.ItemAdded:Fire(player, itemId)
self.Client.ItemAdded:FireAll(itemId)
self.Client.ItemAdded:FireExcept(player, itemId)
Client code:
local Inventory = Rivet:Get("Inventory")
Inventory.ItemAdded:Connect(function(itemId: string)
print("Received item:", itemId)
end)
Signal dispatch flow:
- Server validates
Payloadcontracts. - Server encodes custom payload values.
- Server fires one client, all clients, or every client except one.
- Client proxy receives payload values.
- Client proxy decodes custom payload values.
- Client callback runs with decoded values.
ItemAdded, CoinsChanged, RoundStarted, and QuestCompleted read well as events. GetItems and EquipItem read like methods and should usually be Query or Action surfaces.
Server Method Signatures
Query and Action server methods receive player first after self.
function Inventory:GetItems(player: Player, category: string)
end
function Inventory:EquipItem(player: Player, itemId: string)
end
Client calls do not pass the player:
Inventory:GetItems("Weapons")
Inventory:EquipItem("Sword")
Rivet gets the player from Roblox's remote call and injects it into the server method.
Client Proxy Shape
Client Rivet:Get("Inventory") returns a proxy with declared surface names.
local Inventory = Rivet:Get("Inventory")
local items = Inventory:GetItems("Weapons")
Inventory:EquipItem("Sword")
Inventory.ItemAdded:Connect(function(itemId)
print(itemId)
end)
The proxy is intentionally limited. It does not contain private server methods. It also cannot resolve server dependencies through proxy:Get; client network proxies reject dependency lookup.
Debug Network Stats
Enable network debug counters at startup:
Rivet.Start({
Roots = {
ReplicatedStorage.Units,
},
Debug = {
Network = true,
},
})
Then inspect a snapshot:
local stats = Rivet.Debug:GetNetworkStats()
Shape:
{
Inventory = {
GetItems = {
Kind = "Query",
Calls = 12,
Failures = 0,
},
},
}
Debug mode also fires plugin hooks:
OnNetworkCall(context)OnNetworkError(context)
The context includes UnitId, SurfaceName, Kind, and either ArgCount or Message.
Security Model
Rivet does not magically make client requests trustworthy. It makes the boundary explicit and validates declared runtime shapes. You still own game rules.
function Shop:BuyItem(player: Player, itemId: string)
local profile = self.Data:GetProfile(player)
local item = self.Catalog:GetItem(itemId)
if item == nil then
return
end
if profile.Coins < item.Price then
return
end
profile.Coins -= item.Price
self.Inventory:AddItem(player, itemId)
end
Contracts can prove that itemId is a string. They cannot prove that the player is allowed to buy that item. Keep authorization, rate limiting, and gameplay validation on the server.
Troubleshooting
If a client proxy is missing:
- confirm the server started Rivet successfully
- confirm
ReplicatedStorage.RivetRemotesexists - confirm the Unit has a Client surface
- confirm Query and Action methods exist
- confirm the client started after remotes were created
- confirm the client uses the same Rivet package path as the server
If a call fails:
- read the error for
Unit.Surface - check
Args,Returns, orPayloadcontracts - check custom codecs are registered on the side that needs them
- inspect
Rivet.Debug:GetNetworkStats()when network debug is enabled - use plugin
OnNetworkErrorif you want central logging
Next: Contracts.