update rules

This commit is contained in:
Joe 2026-02-02 14:05:11 -06:00
parent 4c390fdd37
commit d5522b2fef
8549 changed files with 11783 additions and 1888 deletions

View file

@ -0,0 +1,187 @@
---
title: Properties and attributes
description: How to use scripts to manipulate object properties and attributes.
---
Making experiences interactive often means manipulating object properties and attributes:
- Properties are part of the object class. For example, the `Class.BasePart.Anchored` property controls physics for the part. In a track and field experience, you might want to anchor a discus or javelin the instant it lands so that players have a visual indicator of how far it traveled.
- Attributes are essentially custom properties that you define. For example, the [Plant](../resources/plant-reference-project.md) reference project uses attributes to set the purchase price for seeds and the maximum plant size that a pot can hold.
## Replication order
Before you begin retrieving and manipulating objects, you must have an understanding of replication order.
The Roblox Engine doesn't guarantee the order in which objects are replicated from the server to the client, which makes the `Class.Instance:WaitForChild()` method essential for accessing objects in client scripts, particularly objects in the `Class.Workspace`. Still, some aspects of the process are predictable:
1. The client loads the contents of `Class.ReplicatedFirst`, such as a loading screen, assets, and scripts.
1. `Class.LocalScript|LocalScripts` (and `Class.Script|Scripts` with a `Class.Script.RunContext|RunContext` of `Enum.RunContext.Client|Client`) in `ReplicatedFirst` run. These scripts can safely get objects from `ReplicatedFirst` without using `WaitForChild()`:
```lua
-- Safe
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local LoadingScreen = require(ReplicatedFirst.LoadingScreen)
```
These scripts **can't** safely get objects from other [services](services.md), because they might not have loaded yet:
```lua
-- Not safe
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PickupManager = require(ReplicatedStorage.PickupManager)
```
You **can** use `WaitForChild()` in these scripts to get objects from other services, but doing so negates the benefits of using `ReplicatedFirst`.
1. The client continues loading the rest of the experience.
1. When it finishes, the `Class.DataModel.Loaded|game.Loaded` event fires and `Class.DataModel:IsLoaded()|game:IsLoaded()` returns true.
1. `LocalScripts` in `Players.Player.PlayerScripts` (copied from `StarterPlayerScripts`) run, as well as client `Scripts` in `Class.ReplicatedStorage`. These scripts can safely get objects from `ReplicatedStorage` without using `WaitForChild()`.
1. The player's `Class.Player.Character|Character` model spawns in the experience.
1. `LocalScripts` in `Workspace.Character` (copied from `StarterCharacterScripts`) run.
If your experience uses [instance streaming](../workspace/streaming.md) (`Class.Workspace.StreamingEnabled`), some or most objects might not have loaded into the workspace, so using `WaitForChild()` to access workspace objects becomes an even more important safety measure. In particular, see [Stream in](../workspace/streaming.md#stream-in) and [Per-model streaming controls](../workspace/streaming.md#per-model-streaming-controls) for additional information on loading and tuning streaming behavior.
## Get objects
The first step to modifying object properties and attributes is to get a reference to the object. The simplest solution is to make the script a child of the object in the **Explorer** and use `script.Parent` to reference the object.
<img alt="A script parented to a model in the Explorer." src="../assets/studio/explorer/Script-Parent-Model.png" width="320" />
```lua
local sign = script.Parent
```
The more universal solution is to get the object from a [service](services.md) using methods like `Class.Instance:FindFirstChild()` or `Class.Instance:WaitForChild()`.
<img alt="A Model within a Folder in ReplicatedStorage." src="../assets/studio/explorer/Script-Parent-ReplicatedStorage.png" width="320" />
```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local signsFolder = ReplicatedStorage:WaitForChild("Signs")
local sign = signsFolder:WaitForChild("InteractiveSign")
```
## Modify properties
Properties are straightforward to access — just use a `.` after the object reference&nbsp;— although if you're working with a model, you might need to choose an individual part rather than the model itself.
<img alt="A Model within ReplicatedStorage." src="../assets/studio/explorer/ReplicatedStorage-Model.png" width="320" />
```lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local chair = ReplicatedStorage:WaitForChild("Chair")
chair.LeftArmRest.Size = Vector3.new(10, 1, 10)
```
## Create attributes
Although you can create attributes programmatically, the more common solution is to create them with default values in the Studio user interface. Then you can use scripts to modify their values in response to player actions.
<img alt="A script within a folder in ReplicatedStorage." src="../assets/studio/properties/Attributes-Example.png" width="320" />
For information on creating attributes in Studio, see [Instance attributes](../studio/properties.md#instance-attributes).
## Set attributes
To modify an attribute's value, call `Class.Instance:SetAttribute()` with a name and value.
```lua title="Create or Modify Attribute"
local cabbage = script.Parent
cabbage:SetAttribute("Harvestable", true)
```
If the attribute doesn't already exist, this method creates it.
## Get attribute values
To get the value of one existing attribute, call `Class.Instance:GetAttribute()` on the instance.
```lua title="Get Attribute Value"
local cabbage = script.Parent
cabbage:SetAttribute("Harvestable", true)
local isHarvestable = cabbage:GetAttribute("Harvestable")
print(isHarvestable) --> true
```
Similarly, you can get all attributes by calling `Class.Instance:GetAttributes()`. This method returns a dictionary of key-value pairs.
```lua title="Get All Attributes"
local cabbage = script.Parent
local cabbageAttributes = cabbage:GetAttributes()
print(cabbageAttributes.GrowthRate) --> 2
for k, v in cabbageAttributes do
print(k, v)
end
```
## Delete attributes
To delete an attribute, set its value to `nil`.
```lua title="Delete Attribute"
local cabbage = script.Parent
cabbage:SetAttribute("GrowthRate", nil)
```
## Detect changes
There are several ways to listen for changes to properties and attributes:
- The `Class.Instance.AttributeChanged` event listens for changes to any attribute and passes the name of the changed attribute as a parameter.
- The `Class.Instance:GetPropertyChangedSignal()` method lets you listen for changes to one property and passes no parameters.
- The `Class.Instance:GetAttributeChangedSignal()` method lets you listen for changes to one attribute and passes no parameters.
Due to the minimal information that these events and methods pass as parameters, all of them are a good fit for anonymous functions, particularly `Class.Instance:GetPropertyChangedSignal()` and `Class.Instance:GetAttributeChangedSignal()`. To learn more about anonymous functions and working with events, see [Events](events/index.md).
```lua title="Listen for Changes"
local cabbage = script.Parent
-- Local functions
local function onAnyPropertyChange(property)
-- Ignore changes to attributes
if property ~= "Attributes" then
print(property) --> Name
print(cabbage[property]) --> Cabbage1
end
end
local function onAnyAttributeChange(attribute)
print(attribute) --> Grow, GrowthRate
print(cabbage:GetAttribute(attribute)) --> false, 3
end
-- Listen for changes and connect to local functions
cabbage.Changed:Connect(onAnyPropertyChange)
cabbage.AttributeChanged:Connect(onAnyAttributeChange)
-- Listen for changes and connect to anonymous functions
cabbage:GetPropertyChangedSignal("Name"):Connect(function()
print(cabbage.Name) --> Cabbage1
end)
cabbage:GetAttributeChangedSignal("GrowthRate"):Connect(function()
print(cabbage:GetAttribute("GrowthRate")) --> 3
end)
-- Fires Changed and GetPropertyChangedSignal()
cabbage.Name = "Cabbage1"
-- Fires Changed and AttributeChanged
cabbage:SetAttribute("Grow", false)
-- Fires Changed, AttributeChanged, and GetAttributeChangedSignal()
cabbage:SetAttribute("GrowthRate", 3)
```

View file

@ -0,0 +1,268 @@
---
title: Script capabilities
description: Script Capabilities is a system that controls which actions scripts can perform.
---
<Alert severity="success">
This page describes a feature that is still experimental and is available as a client beta. Features described here might undergo additional changes based on developer feedback and/or bug fixes.
</Alert>
**Script capabilities** is a system that offers control over actions that scripts can perform inside the `Class.DataModel` subtree. It provides better control over experience scripts rather than being an "all or nothing" system where any script can do anything that other scripts can.
- This system lets you limit what models taken from the toolbox can do and makes it easier to include user-generated content inside the experience, even those that contain scripts.
- It can also help ensure better security of experiences that allow players to run their own code, which is often executed in a restricted or emulated environment.
- It can also be used to share libraries that restrict what they can do themselves. For example, a library providing additional math methods can be restricted to the smallest set of capabilities it needs so that other developers using that library don't have to validate the entire codebase to make sure it doesn't include malicious code.
## Enable script capabilities
To enable this feature, change the `Class.Workspace.SandboxedInstanceMode|SandboxedInstanceMode` setting from `Default` to `Experimental` in the **Explorer**.
When the client beta test is completed, this step will no longer be required.
## Sandboxed container
This system introduces a concept of a **sandboxed container**.
An instance of type `Class.Model`, `Class.Folder`, `Class.Script`, or descendants of any of those classes have a `Class.Instance.Sandboxed|Sandboxed` property available in the Studio **Properties** window, under the **Permissions** section.
<img src="../assets/studio/properties/Folder-Sandboxed.png" width="320" alt="Sandboxed property of a Folder in the Properties window." />
Enabling the `Class.Instance.Sandboxed|Sandboxed` property designates the instance as a sandboxed container inside the `Class.DataModel` tree, which limits the actions that the scripts inside that container can perform based on the set of values in the `Class.Instance.Capabilities|Capabilities` property.
## Capabilities
The `Capabilities` property is a set of values that control different aspects of execution, split into four groups:
- **Execution control** - Specifies if a script can run on client or server
- **Instance access control** - Specifies which `Class.DataModel` parts a script can interact with
- **Script functionality control** - Specifies which Luau functions scripts can call
- **Engine API access control** - Specifies which parts of the Roblox Engine API can be used
When a script attempts to perform an action that is not in the set of capabilities for the current execution state, an error is reported. Errors typically include the action being attempted, the target of an action, and the first capability that is missing, for example:
```text
The current thread cannot modify 'Workspace' (lacking capability AccessOutsideWrite)
The current thread cannot call 'Clone' (lacking capability CreateInstances)
The current thread cannot call 'GetSecret' (lacking capability Network)
```
### Execution control
This set includes two capabilities:
- **RunClientScript** - `Class.LocalScript` or `Class.Script` with a `Class.BaseScript.RunContext|RunContext` value of `Enum.RunContext.Client|Client` is allowed to execute on the client
- **RunServerScript** - `Class.Script` with a `Class.BaseScript.RunContext|RunContext` value of `Enum.RunContext.Server|Server` is allowed to execute on the server
If the script is `Class.BaseScript.Enabled|Enabled`, but the capability corresponding to the location it attempts to start in is not available, a warning message is displayed in the **Output** window. If a script wasn't supposed to run in that context, disable or delete it.
Note that `Class.ModuleScript|ModuleScripts` are not required to have these execution capabilities to be required.
When a script fails to start because the execution control capability is missing, it is reported as a warning in the output, for example:
```text
Cannot start server script 'Script' (lacking capability RunServerScript)
```
### Instance access control
This set includes only a single capability:
- **AccessOutsideWrite** - Script is allowed to fetch and receive instances from outside the sandboxed container
When the capability is not available, the script can only look up instances that are inside its own sandboxed container. For example, if the script is placed directly in the sandboxed container, `script.Parent.Parent` returns `nil`.
Additionally, any Roblox API event that passes in an `Class.Instance` instead passes in `nil` for any `Class.Instance` outside the sandboxed container. For example, if the `Class.BasePart.Touched` is signalled by a touch from an instance outside the sandboxed container, the event is still received, but the argument is `nil`.
Avoid setting this capability; sandboxing guarantees are weaker when scripts can interact with any instance in an experience.
#### Access to services
Even without **AccessOutsideWrite**, scripts in the sandboxed container can still access to `game`, `workspace`, and services. This access is provided so that scripts can still call useful methods of those globals, like `Class.DataModel.GetService`, but access to their child instances is still validated.
#### Internally passed instances
If an instance is passed through a function call that doesn't go through Roblox APIs, the reference is preserved. However, if a `Class.ModuleScript` is passed in this way, it can't be required without **AccessOutsideWrite**. This is because the return of the `Class.ModuleScript` is often mutable and can be modified by a script in a sandboxed container.
### Script functionality control
This set of capabilities controls some general aspects of scripts:
- **AssetRequire** - Script is allowed to call `Global.LuaGlobals.require` with an asset ID
- **LoadString** - Script is allowed to call `Global.LuaGlobals.loadstring`
- **ScriptGlobals** - Script has `Global.LuaGlobals.shared` and `Global.LuaGlobals._G` available
- **CreateInstances** - Script can create new instances using `Datatype.Instance.new`, `Datatype.Instance.fromExisting`, or `Class.Instance:Clone`
Keep in mind that default function restrictions still apply. Even if **LoadString** is enabled, the experience still has to turn enable it in `Class.ServerScriptService`, and it is still only available on the server.
To create new instances, aside from **CreateInstances**, an additional Engine API capability providing access to that instance is required.
### Engine API access control
This last group of capabilities controls script access to various Engine APIs:
- **Basic** - Access to simple instances and essential building blocks
- **Audio** - Access to instances related to audio APIs
- **DataStore** - Access to data store and memory store APIs
- **Network** - Access to HTTP networking APIs
- **Physics** - Access to instances related to physics
- **UI** - Access to instances related to user interfaces
- **CSG**: access to instances and functions related to constructive solid geometry (CSG)
- **Chat**: access to instances related to in-experience chat
- **Animation**: access to instances related to animations
- **Avatar**: access to instances related to avatars
- **Input**: access to instances related to user input
- **Environment**: access to instances related to the control of how the environment is displayed
- **RemoteEvent**: access to instances for internal networking operations
There are also instances that are available without any capabilities aside from being able to execute the scripts. These include the following `Class.HttpService` methods:
- `Class.HttpService:JSONDecode()`
- `Class.HttpService:JSONEncode()`
- `Class.HttpService:GenerateGUID()`
If an instance property or method is accessed without a required capability, an error is reported describing the missing capability.
Finally, capabilities do not cover every instance in the Roblox Engine today. Instances not listed in this section or the following one are not available for interaction from a sandboxed container and throw an error saying that an **Unassigned** capability is not available to the current script.
An additional limitation is that `Global.LuaGlobals.getfenv` and `Global.LuaGlobals.setfenv` functions are not available for scripts in a sandboxed container.
Only script access to instances is limited. The instances themselves can still exist and operate by themselves inside a sandboxed container. Lights still shine, user interfaces are still visible, and audio setups that are already wired play sounds.
#### Engine API capability assignments
<Alert severity="success">
This feature is currently in the experimental stage, so the assignments of the APIs are not final. The capability of each instance is displayed on the documentation page for that instance.
</Alert>
Here is the list of instances and methods (if different from the instance capability) for each Engine API capability:
- **Basic**
- `Class.Attachment`
- `Class.Beam`, `Class.ParticleEmitter`, `Class.Smoke`, `Class.Sparkles`, `Class.Trail`
- `Class.SurfaceAppearance`, `Class.Decal`, `Class.FaceInstance`, `Class.Texture`
- `Class.Light`, `Class.PointLight`, `Class.SpotLight`
- `Class.Part`, `Class.MeshPart`, `Class.CornerWedgePart`, `Class.TriangleMeshPart`, `Class.TrussPart`, `Class.WedgePart`
- `Class.FloatCurve`, `Class.RotationCurve`, `Class.EulerRotationCurve`, `Class.Vector3Curve`
- `Class.Tween`, `Class.TweenService`
- `Class.Wire`
- `Class.WorldModel`
- `Class.CollectionService`, `Class.Debris`, `Class.RunService`
- **Audio**
- `Class.AudioAnalyzer`, `Class.AudioChorus`, `Class.AudioCompressor`, `Class.AudioDistortion`, `Class.AudioEcho`, `Class.AudioEqualizer`, `Class.AudioFader`, `Class.AudioFader`, `Class.AudioFilter`, `Class.AudioFlanger`, `Class.AudioLimiter`, `Class.AudioPitchShifter`, `Class.AudioReverb`
- `Class.AudioEmitter`, `Class.AudioPlayer`
- `Class.AudioDeviceOutput`, `Class.AudioListener`
- **DataStore**
- `Class.DataStore`, `Class.OrderedDataStore`, `Class.GlobalDataStore`
- `Class.DataStoreService`, `Class.MemoryStoreService`
- `Class.DataStoreGetOptions`, `Class.DataStoreIncrementOptions`, `Class.DataStoreInfo`, `Class.DataStoreKey`, `Class.DataStoreKeyInfo`, `Class.DataStoreObjectVersionInfo`, `Class.DataStoreOptions`, `Class.DataStoreSetOptions`
- `Class.MemoryStoreHashMap`, `Class.MemoryStoreQueue`, `Class.MemoryStoreSortedMap`
- **Network**
- `Class.HttpService:GetAsync`, `Class.HttpService:RequestAsync`, `Class.HttpService:PostAsync`, `Class.HttpService:UrlEncode`, `Class.HttpService:GetSecret`
- **Physics**
- `Class.AlignOrientation`, `Class.AlignPosition`, `Class.DynamicRotate`
- `Class.JointInstance`, `Class.Motor`, `Class.Motor6D`
- `Class.AngularVelocity`, `Class.LinearVelocity`, `Class.LineForce`, `Class.Torque`, `Class.VectorForce`
- `Class.Weld`
- `Class.Constraint`, `Class.BallSocketConstraint`, `Class.CylindricalConstraint`, `Class.HingeConstraint`, `Class.NoCollisionConstraint`, `Class.PlaneConstraint`, `Class.PrismaticConstraint`, `Class.RigidConstraint`, `Class.RodConstraint`, `Class.RopeConstraint`, `Class.SlidingBallConstraint`, `Class.SpringConstraint`, `Class.TorsionSpringConstraint`, `Class.UniversalConstraint`, `Class.WeldConstraint`
- **UI**
- `Class.BasePlayerGui`, `Class.PlayerGui`, `Class.BillboardGui`, `Class.GuiBase`, `Class.GuiBase2d`, `Class.GuiBase3d`, `Class.LayerCollector`, `Class.ScreenGui`, `Class.SurfaceGui`, `Class.SurfaceGuiBase`, `Class.UIBase`
- `Class.CanvasGroup`, `Class.Frame`, `Class.ScrollingFrame`
- `Class.GuiButton`, `Class.GuiLabel`, `Class.GuiObject`, `Class.ImageButton`, `Class.ImageLabel`
- `Class.Dialog`, `Class.DialogChoice`, `Class.ProximityPrompt`
- `Class.TextBox`, `Class.TextButton`, `Class.TextFilterResult`, `Class.TextFilterTranslatedResult`, `Class.TextLabel`, `Class.TextService`, `Class.GetTextBoundsParams`
- `Class.Path2D`
- `Class.UIComponent`, `Class.UICorner`, `Class.UIDragDetector`, `Class.UIFlexItem`, `Class.UIGradient`, `Class.UIGridLayout`, `Class.UIGridStyleLayout`, `Class.UILayout`, `Class.UIListLayout`, `Class.UIPadding`, `Class.UIPageLayout`, `Class.UIScale`, `Class.UIStroke`, `Class.UITableLayout`
- `Class.UIConstraint`, `Class.UIAspectRatioConstraint`, `Class.UISizeConstraint`, `Class.UITextSizeConstraint`
- `Class.VideoFrame`, `Class.ViewportFrame`
- **CSG**
- `Class.GeometryService`
- `Class.BasePart` methods: `Class.BasePart:IntersectAsync`, `Class.BasePart:SubtractAsync`, `Class.BasePart:UnionAsync`
- `Class.IntersectOperation` (also requires **Basic**), `Class.NegateOperation` (also requires **Basic**), `Class.PartOperation` (also requires **Basic**), `Class.UnionOperation` (also requires **Basic**)
- **Chat**
- `Class.BubbleChatConfiguration`, `Class.BubbleChatMessageProperties`
- `Class.ChannelTabsConfiguration`
- `Class.ChatInputBarConfiguration`, `Class.ChatWindowConfiguration`, `Class.ChatWindowMessageProperties`
- `Class.TextChannel`, `Class.TextChatCommand`, `Class.TextChatConfigurations`, `Class.TextChatMessage`, `Class.TextChatMessageProperties`, `Class.TextChatService`, `Class.TextSource`
- **Animation**
- `Class.Bone` (also requires **Basic**)
- `Class.Animation`, `Class.AnimationClip`, `Class.AnimationClipProvider`, `Class.AnimationController`, `Class.AnimationRigData`, `Class.AnimationTrack`, `Class.Animator`
- `Class.CurveAnimation`
- `Class.FaceControls`, `Class.IKControl`
- `Class.Keyframe`, `Class.KeyframeMarker`, `Class.KeyframeSequence`
- `Class.MarkerCurve`
- `Class.PoseBase`, `Class.Pose`, `Class.NumberPose`
- **Avatar**
- `Class.Accessory`, `Class.AccessoryDescription`
- `Class.Accoutrement`, `Class.Clothing`, `Class.Shirt`, `Class.Pants`
- `Class.BaseWrap`, `Class.WrapDeformer`, `Class.WrapLayer`, `Class.WrapTarget`
- `Class.BodyColors`, `Class.BodyPartDescription`, `Class.CharacterAppearance`, `Class.ShirtGraphic`
- `Class.CharacterMesh`
- `Class.Humanoid`, `Class.HumanoidDescription`
- **Input**
- `Class.GamepadService`, `Class.KeyboardService`, `Class.MouseService`
- `Class.ContextActionService`, `Class.TouchInputService`, `Class.UserInputService`
- `Class.InputObject`, `Class.Mouse`, `Class.PlayerMouse`
- **Environment**
- `Class.Atmosphere`, `Class.Clouds`, `Class.Lighting`, `Class.Sky`
- `Class.BloomEffect`, `Class.BlurEffect`, `Class.ColorCorrectionEffect`, `Class.ColorGradingEffect`, `Class.DepthOfFieldEffect`, `Class.PostEffect`, `Class.SunRaysEffect`
- **RemoteEvent**
- `Class.BaseRemoteEvent`, `Class.RemoteEvent`, `Class.UnreliableRemoteEvent`
- `Class.RemoteFunction`
## Interactions between containers
### Nested containers
When one sandboxed container is nested inside another one, the instances of the inner container are accessible to the outer one.
Capabilities of the inner container are limited by the capabilities of the outer one.
For example, if the outer container has capabilities of **Basic**, **Audio** and **CSG**, while the inner container has **Basic** and **Network**, only **Basic** capabilities are available to the inner container at runtime.
If there are no capabilities in common between the inner and outer containers, the resulting capability set is empty.
### Bindable functions and events
`Class.BindableEvent` and `Class.BindableFunction` provide the best way to communicate with the container or allow it to run callbacks with capabilities it itself is not allowed to use directly.
When an event or a function is fired, connections are executed in the context of the function that registered them. This means that if the event or function callback is registered by the sandboxed container, it is called with the capabilities of that container. If the callback is registered by the code outside, when sandboxed container scripts invoke them, they execute your functions with capabilities available to your functions.
It is important to note that even with the **AccessOutsideWrite** capability, scripts in sandboxed containers cannot invoke events or functions outside their containers if they have a larger capability set than the container itself.
### Module require
Inner `Class.ModuleScript|ModuleScripts` can be required by the sandboxed container as usual.
However, if the target instance is outside the container, the `Class.ModuleScript` can only be required if the capability set that is available to it is smaller or equal to the capabilities available to the container.
This limitation does not apply to **RunClientScript** and **RunServerScript** capabilities. If the `Class.ModuleScript` is placed in a container with only **RunClientScript** but is required from a script that has the **RunServerScript** capability, it is allowed to succeed and run those functions on the server.
### Directly called functions
If a `Class.ModuleScript` in a sandboxed container is required from outside the container, some of the protections are not available. In particular, the target function is able to access all instances available to the caller. If the caller is not in a sandboxed container, the call acts as if **AccessOutsideWrite** is available to it.
Other capability restrictions still apply. If you have a **DataStore** access capability, but the target module does not, it is unable to call `Class.DataStore` methods. However, if you pass your own function working with `Class.DataStore`, the target can run it during that call.
If the target schedules a thread using methods like those from `Library.task`, those threads lose the ability to call that function.
Instances can be passed to the target module or assigned to the module fields.
If required, it is recommended to assign table members using `Global.LuaGlobals.rawset` to avoid running `__index`/`__newindex` metamethods that might be set on the table.
The overall recommendation is to communicate with `Class.BindableEvent` and `Class.BindableFunction` whenever possible.
### Movement of instances
Most instances do not have restrictions on movement between containers. Script instances, however, can only be moved into a container that has the same set of capabilities or a subset of those capabilities.
This means that sandboxed container with **AccessOutsideWrite** cannot just re-parent a script inside itself to outside and get more capabilities.

View file

@ -0,0 +1,123 @@
---
title: Debounce patterns
description: Debounce patterns are coding techniques that prevent a function from running too many times.
---
A **debounce** pattern is a coding technique that prevents a function from running too many times or an input from triggering multiple times. The following scripting scenarios illustrate debounce as a best practice.
## Detect collisions
Suppose you want to create a hazardous trap part that inflicts 10 damage when touched. An initial implementation might use a basic `Class.BasePart.Touched` connection and a `damagePlayer` function like this:
```lua title="Script - Damage Player"
local part = script.Parent
local function damagePlayer(otherPart)
print(part.Name .. " collided with " .. otherPart.Name)
local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
if humanoid then
humanoid.Health -= 10 -- Reduce player health
end
end
part.Touched:Connect(damagePlayer)
```
While logical at first glance, testing will show that the `Class.BasePart.Touched|Touched` event fires multiple times in quick succession based on subtle physical collisions.
<img src="../assets/scripting/scripts/Touched-Event-No-Debounce.png" width="780" />
To avoid causing excessive damage on initial contact, you can add a debounce system which enforces a cooldown period on damage through an [instance attribute](../studio/properties.md#instance-attributes).
```lua title="Script - Damage Player Using Debounce" highlight="10, 11, 13, 14"
local part = script.Parent
local RESET_TIME = 1
local function damagePlayer(otherPart)
print(part.Name .. " collided with " .. otherPart.Name)
local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
if humanoid then
if not part:GetAttribute("Touched") then
part:SetAttribute("Touched", true) -- Set attribute to true
humanoid.Health -= 10 -- Reduce player health
task.wait(RESET_TIME) -- Wait for reset duration
part:SetAttribute("Touched", false) -- Reset attribute
end
end
end
part.Touched:Connect(damagePlayer)
```
## Trigger sounds
Debounce is also useful when working with sound effects, such as playing a sound when two parts collide (`Class.BasePart.Touched|Touched`), or playing a sound on the `Class.GuiButton.Activated|Activated` event when a user interacts with an on-screen button. In both cases, calling `Class.Sound:Play()` starts playback from the beginning of its track and&nbsp;&mdash; without a debounce system&nbsp;&mdash; the sound may play multiple times in quick succession.
To prevent sound overlap, you can debounce using the `Class.Sound.IsPlaying|IsPlaying` property of the `Class.Sound` object:
```lua title="Script - Play Collision Sound Using Debounce" highlight="5, 7-9"
local projectile = script.Parent
local function playSound()
-- Find child sound on the part
local sound = projectile:FindFirstChild("Impact")
-- Play the sound only if it's not already playing
if sound and not sound.IsPlaying then
sound:Play()
end
end
projectile.Touched:Connect(playSound)
```
```lua title="Script - Play Button Click Using Debounce" highlight="5, 7-9"
local button = script.Parent
local function onButtonActivated()
-- Find child sound on the button
local sound = button:FindFirstChild("Click")
-- Play the sound only if it's not already playing
if sound and not sound.IsPlaying then
sound:Play()
end
end
button.Activated:Connect(onButtonActivated)
```
## Pickup effects
Experiences often include collectible pickups in the 3D world such as medkits, ammo packs, and more. If you design these pickups to remain in the world for players to grab again and again, a "cooldown" time should be added before the pickup refreshes and reactivates.
Similar to [detecting collisions](#detect-collisions), you can manage the debounce state with an [instance attribute](../studio/properties.md#instance-attributes), and visualize the cooldown period by changing the part's `Class.BasePart.Transparency|Transparency`.
```lua title="Script - Health Pickup Using Debounce" highlight="10, 11, 13, 15-17"
local part = script.Parent
part.Anchored = true
part.CanCollide = false
local COOLDOWN_TIME = 5
local function healPlayer(otherPart)
local humanoid = otherPart.Parent:FindFirstChildWhichIsA("Humanoid")
if humanoid then
if not part:GetAttribute("CoolingDown") then
part:SetAttribute("CoolingDown", true) -- Set attribute to true
humanoid.Health += 25 -- Increase player health
part.Transparency = 0.75 -- Make part semi-transparent to indicate cooldown state
task.wait(COOLDOWN_TIME) -- Wait for cooldown duration
part.Transparency = 0 -- Reset part to fully opaque
part:SetAttribute("CoolingDown", false) -- Reset attribute
end
end
end
part.Touched:Connect(healPlayer)
```
<Alert severity="info">
This cooldown concept can be applied to other scenarios as well, such as creating a "reload" time for a rocket launcher to limit how frequently players can fire rockets.
</Alert>

View file

@ -0,0 +1,250 @@
---
title: Bindable events and callbacks
description: Bindable events and callbacks allow for back-and-forth communication on the same side of the client-server boundary.
---
`Class.BindableEvent` and `Class.BindableFunction` objects let you bind behaviors between scripts **on the same side** of the [client-server](../../projects/client-server.md) boundary and communicate a specific desired outcome for in-experience actions.
The most common use case for bindable events is for experiences that have a round-based structure. For example, you might have a "match started" event that lets other scripts start a timer and display a leaderboard, with a corresponding "match ended" event that lets other scripts know when to move players back into a lobby and display the winners.
Because they coordinate activities between scripts, bindable events are typically used on the server, but you can use them on the client, too.
Depending on how your experience works, bindable events can help make your code more modular, but [module scripts](../module.md) are often a better alternative for situations in which you need to share data between scripts. You can also use bindable events in conjunction with module scripts for a cleaner syntax, as noted in [Custom events](../module.md#custom-events).
<Alert severity="info">
To communicate between scripts **across** the client-server boundary, see [Remote events](remote.md).
</Alert>
## Bindable events
The `Class.BindableEvent` object enables custom events through asynchronous, one-way communication between scripts.
When you fire a `Class.BindableEvent` through the `Class.BindableEvent:Fire()|Fire()` method, the firing script does **not** yield, and the target function receives the passed arguments with certain [limitations](#argument-limitations). Like all events, `Class.BindableEvent|BindableEvents` create threads of each connected function, so even if one errors, others continue.
To create a new `Class.BindableEvent` using the [Explorer](../../studio/explorer.md) window in Studio:
1. Hover over the container into which you want to insert the `Class.BindableEvent`. We recommend using `Class.ServerScriptService` for communication between server scripts and `Class.ReplicatedStorage` for communication between client scripts.
2. Click the **&CirclePlus;** button that appears to the right of the container's name and insert a **BindableEvent** instance.
3. Rename the instance to `TestBindableEvent`.
After you've created a `Class.BindableEvent`, connect a function to its `Class.BindableEvent.Event|Event` event in one script, and then `Class.BindableEvent:Fire()|Fire()` the event from another script.
```lua title="Event Connection"
local ServerScriptService = game:GetService("ServerScriptService")
-- Get reference to bindable event instance
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
-- Connect anonymous function to event
bindableEvent.Event:Connect(function(data)
print(data) --> Round started!
end)
```
```lua title="Event Firing"
local ServerScriptService = game:GetService("ServerScriptService")
-- Get reference to bindable event instance
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
-- Fire bindable event
bindableEvent:Fire("Round started!")
```
<Alert severity="warning">
You can connect multiple functions to the same `Class.BindableEvent`, but Luau executes them in an unpredictable order. To ensure that functions execute in a particular order, combine them into a single function and connect it to the event.
</Alert>
## Custom callbacks
The `Class.BindableFunction` object allows for synchronous, two-way communication between scripts. You can use it to define a custom callback function and invoke it manually by calling `Class.BindableFunction:Invoke()`. The code invoking the function **yields** until the corresponding callback is found, and the callback receives the arguments that you passed to `Class.BindableFunction:Invoke()|Invoke()`. If the callback was never set, the script that invokes it doesn't resume execution.
To create a new `Class.BindableFunction` using the [Explorer](../../studio/explorer.md) window in Studio:
1. Hover over the container into which you want to insert the `Class.BindableFunction`. We recommend using `Class.ServerScriptService` for communication between server scripts and `Class.ReplicatedStorage` for communication between client scripts.
1. Click the **&CirclePlus;** button that appears to the right of the container's name and insert a **BindableFunction** instance.
1. Rename the instance to `TestBindableFunction`.
Once you've created a `Class.BindableFunction`, you can connect to its `Class.BindableFunction.OnInvoke|OnInvoke` callback in one script, then `Class.BindableFunction:Invoke()|Invoke()` the callback function from another script.
```lua title="Callback Connection"
local ServerScriptService = game:GetService("ServerScriptService")
-- Get reference to bindable function
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
-- Callback function
local function addTwoNumbers(a, b)
return a + b
end
-- Set function as bindable function's callback
bindableFunction.OnInvoke = addTwoNumbers
```
```lua title="Event Invocation"
local ServerScriptService = game:GetService("ServerScriptService")
-- Get reference to bindable function
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
-- Invoke callback function and output returned value
local sum = bindableFunction:Invoke(2, 4)
print(sum) --> 6
```
<Alert severity="warning">
Each `Class.BindableFunction` can only utilize one `Class.BindableFunction.OnInvoke|OnInvoke` callback. If you make multiple definitions, only the last one assigned runs. Also note that if the `Class.BindableFunction.OnInvoke|OnInvoke` callback does not have a `return` statement, the invocation returns `nil`.
</Alert>
## Argument limitations
When you fire a `Class.BindableEvent` or invoke a `Class.BindableFunction`, it forwards any arguments that you pass with the event or to the callback function. You can pass any type of Roblox object (`Datatype.Enum`, `Class.Instance`, etc.), as well as Luau types like numbers, strings, and booleans, although you should carefully consider the following limitations.
### Non-string indices
If any **indices** of a passed table are non-string types, such as an `Class.Instance`, [userdata](../../luau/userdata.md), or [function](../../luau/functions.md), Roblox automatically converts those indices to strings.
```lua title="Event Connection"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
local function onEventFire(passedTable)
for k, v in passedTable do
print(typeof(k)) --> string
end
end
-- Connect function to event
bindableEvent.Event:Connect(onEventFire)
```
```lua title="Event Firing"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
-- Fire event with table containing a workspace instance as a key
bindableEvent:Fire({
[workspace.Baseplate] = true
})
```
### Table indexing
If you pass a table of data, do not pass a mixed table of numeric and string keys. Doing so can result in removed elements during the transfer. Instead, pass a table that consists **entirely** of key-value pairs (a dictionary) or **entirely** of numeric indices (an array).
<Alert severity="warning">
In both cases, whether passing a dictionary table or a numerically indexed table, avoid `nil` values for any index.
</Alert>
```lua title="Event Connection"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
local function onEventFire(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
bindableEvent.Event:Connect(onEventFire)
```
```lua title="Event Firing"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
-- Numerically indexed table
local inventoryData = {
"Sword", "Bow"
}
-- Dictionary table
local characterData = {
CharName = "Diva Dragonslayer",
CharClass = "Rogue"
}
-- Fire event with consistently-indexed tables
bindableEvent:Fire(inventoryData)
bindableEvent:Fire(characterData)
```
### Table identities
Tables passed as arguments to bindable events and 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.BindableFunction` and observing how the table identities differ.
```lua title="Callback Connection"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
-- Callback function
local function returnTable(passedTable)
-- Output table identity on invocation
print(tostring(passedTable)) --> table: 0x48eb7aead27563d9
return passedTable
end
-- Set function as bindable function's callback
bindableFunction.OnInvoke = returnTable
```
```lua title="Event Invocation"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableFunction = ServerScriptService:WaitForChild("TestBindableFunction")
local inventoryData = {
"Sword", "Bow"
}
-- Output original table identity
print(tostring(inventoryData)) --> table: 0x059bcdbb2b576549
local invokeReturn = bindableFunction:Invoke(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 title="Event Connection"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
local function onEvent(param)
print(param) --> {["Name"] = "MyTruck"}
end
-- Connect function to event
bindableEvent.Event:Connect(onEvent)
```
```lua title="Event Firing"
local ServerScriptService = game:GetService("ServerScriptService")
local bindableEvent = ServerScriptService:WaitForChild("TestBindableEvent")
local Car = {}
Car.NumWheels = 4
Car.__index = Car
local truck = {}
truck.Name = "MyTruck"
setmetatable(truck, Car)
-- Fire event with table including a metatable
bindableEvent:Fire(truck)
```

View file

@ -0,0 +1,101 @@
---
title: Deferred engine events
description: Deferred engine events defer event handlers until certain resumption points.
---
The `Class.Workspace.SignalBehavior` property controls whether event handlers are fired immediately or deferred. The `Enum.SignalBehavior.Deferred` option is recommended which helps improve the performance and correctness of the engine. The event handlers for **deferred events** are resumed at the next [resumption point](#resumption-points), along with any newly triggered event handlers.
<Alert severity="info">
The `Enum.SignalBehavior.Default` value of `Class.Workspace.SignalBehavior` is currently equivalent to `Enum.SignalBehavior.Immediate`, but will eventually switch to being equivalent to `Enum.SignalBehavior.Deferred`. Template places are directly set to `Enum.SignalBehavior.Deferred` by default.
</Alert>
The following diagram compares the `Enum.SignalBehavior.Immediate|Immediate` event behavior and the `Enum.SignalBehavior.Deferred|Deferred` event behavior.
- With the `Immediate` behavior, if an event triggers another event, the second event handler fires immediately.
- With the `Deferred` behavior, the second event is added to the back of a queue and run later.
The total time taken does not change, but the ordering is different.
<img alt="A comparison of three event handlers firing with Immediate and Deferred behavior" src="../../assets/scripting/scripts/ImmediateVsDeferredEvents.png" width="100%" />
"Re-entrancy" prevents events from continuously firing one another when they reach a certain depth. The current limit for this is 10.
## Deferred event benefits
The `Immediate` behavior has some disadvantages. For every instance added to your experience, property that changes, or some other trigger that is invoked, the engine needs to run Luau code before anything else happens.
- To change 1,000 properties, 1,000 snippets of code potentially need to run after each change.
- Strange, hard-to-diagnose bugs can occur, such as a removing event firing before something was even added.
- Performance-critical systems can fire events requiring them to yield back and forth to Luau.
- Event handlers can make changes to the place or trigger other events any time an event is fired.
- An event can fire multiple times despite being redundant, such as a property changing twice.
By having specific portions of the engine life cycle in which Luau can run, the engine can gain improved performance by using a number of assumptions:
- Performance-critical systems don't need to yield to Luau, which leads to performance gains.
- Unless the engine itself changes it, the place never changes outside of a resumption point.
## Resumption points
After being deferred, an event handler is resumed at the next resumption point. Currently, the set of resumption points includes:
- Input processing (resumes once per input to be processed, see `Class.UserInputService`)
- `Class.RunService.PreRender`
- Legacy waiting script resumption such as `wait()`, `spawn()`, and `delay()`
- `Class.RunService.PreAnimation`
- `Class.RunService.PreSimulation`
- `Class.RunService.PostSimulation`
- Waiting script resumption such as `Library.task.wait()`, `Library.task.spawn()`, and `Library.task.delay()`
- `Class.RunService.Heartbeat`
- `Class.DataModel.BindToClose`
## Common affected code patterns
With remote events, the following examples either stop working correctly or have subtly different behavior; they rely on events being resumed immediately.
### Trigger and catch events mid-execution
In this example, `false` is always returned when deferred events are enabled because the callback has not run. To work correctly, the thread must yield until at least when the event should have fired.
```lua
local success = false
event:Connect(function ()
success = true
end)
doSomethingToTriggerEvent() -- Causes `event` to fire
return success
```
### Listen for the first occurrence of an event
```lua
connection = event:Connect(function ()
connection:Disconnect()
-- do something
end)
```
With deferred events enabled, multiple event handler invocations can be queued before you disconnect from the event. Calling `Class.Instance.Disconnect()|Disconnect()` drops all pending event handler invocations—the same behavior that exists for immediate events.
<Alert severity="warning">
Any other method of disconnection besides `Class.Instance.Disconnect()|Disconnect()`, such as calling `Class.Instance.Destroy()|Destroy()` on the `Class.Instance`, disconnects the signal immediately, but runs the associated event handler for any events that are still pending.
</Alert>
Alternatively, use `Datatype.RBXScriptSignal.Once()|Once()` as a more convenient method for connecting to an event that you only need the first invocation of.
### Events that change ancestry or properties
Deferred events cause events that handle a change in ancestry or a property to fire after the ancestry or property is changed:
```lua
local part = Instance.new("Part", workspace)
local function onPartDestroying()
print("In signal:", part:GetFullName(), #part:GetChildren())
end
part.Destroying:Connect(onPartDestroying)
part:Destroy()
```
Because `Class.Instance.Destroy()|Destroy()` works immediately after the script that called it yields, the instance has already been destroyed by the time `onPartDestroying()` is called. For more examples, see `Class.Instance.Destroying`.

View file

@ -0,0 +1,114 @@
---
title: Events
description: Provides an overview of events and a summary of Roblox's built-in events.
---
Events are occurrences within your experience that you can listen for and respond to. Many Roblox services and objects have built-in events that automatically **fire** in response to specific actions or changes.
For example, a player's `Class.Player.Character|Character` touching a `Class.BasePart` automatically fires a `Class.BasePart.Touched|Touched` event. Each time a player joins your experience, the `Class.Players.PlayerAdded` event fires.
Due to the sheer number of events and client-server architecture, Roblox scripting is often referred to as **event-driven**. This approach is different from many other game engines, which emphasize running code on a frame-by-frame basis.
You don't have to listen for events or take any action in response to them, but the events are firing and available nevertheless. When you do want to respond to an event, you connect a function to it.
<Alert severity="info">
Deferred events can help you ensure more performant and consistent event handling. See [here](deferred.md) for more information.
</Alert>
## Connect functions to events
You connect a function to an event using `Datatype.RBXScriptSignal.Connect()|Connect()` to execute code each time the event fires. Most events pass arguments to their connected functions. For example, the `Class.BasePart.Touched` event passes the object that touched the part (such as a left hand or car wheel), and the `Class.Players.PlayerAdded` event passes the `Class.Player` that joined your experience.
The following code sample demonstrates how to connect a function named `onPartTouched()` to the `Class.BasePart.Touched|Touched` event of a part:
```lua
-- Assumes the script is parented to the part
local part = script.Parent
-- The function you want to run
local function onPartTouched(object)
print("Part was touched by", object:GetFullName())
end
-- Connect the function to the part's Touched event
part.Touched:Connect(onPartTouched)
```
<Alert severity="success">
As a best practice and for optimal performance, it's recommended that you [disconnect](#disconnect-functions-from-events) functions when the connection is no longer needed. Note, however, that this may not **always** be necessary, as all connections to an object's [non-deferred](deferred.md) events are automatically disconnected if an object is destroyed.
</Alert>
You can also connect [anonymous functions](../../luau/functions.md#anonymous-functions) to events when you want to use variables in the parent scope and don't need to use the function elsewhere. For example, this code sample avoids the awkward intermediary function from the similar sample in [Services](../services.md):
```lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SaveManager = require(ReplicatedStorage:WaitForChild("SaveManager"))
local function saveProgress(character)
local position = character:FindFirstChild("HumanoidRootPart").Position
SaveManager.saveData(character, position)
end
-- Anonymous function that calls saveProgress() when a character is removed
-- from the experience (in this case, when the player leaves).
Players.PlayerAdded:Connect(function(player)
player.CharacterRemoving:Connect(saveProgress)
end)
```
## Disconnect functions from events
The `Datatype.RBXScriptSignal.Connect()|Connect()` method returns an `Datatype.RBXScriptConnection` object. If you connect a function to an event, but don't want to call the function the next time an event fires (such as after some condition is met), disconnect it by calling `Datatype.RBXScriptConnection:Disconnect()|Disconnect()` on the `Datatype.RBXScriptConnection` object.
The following code sample shows how to connect and disconnect a function from the `Class.BasePart.Touched|Part.Touched` event:
```lua
local Workspace = game:GetService("Workspace")
local part = Workspace.Part
local targetPart = Workspace.TargetPart
-- Declare an empty placeholder variable for the connection
local connection
local function onPartTouched(otherPart)
if otherPart == targetPart then
print("The part hit the target!")
-- Disconnect the connection
connection:Disconnect()
end
end
-- Connect the above function to the Touched event
connection = part.Touched:Connect(onPartTouched)
```
<Alert severity="success">
If you only want to connect a function to an event once&nbsp;— that is, only run the function the first time the event fires&nbsp;— use the `Datatype.RBXScriptSignal.Once()|Once()` method as a more convenient alternative to connecting and disconnecting the function.
</Alert>
<Alert severity="info">
When Luau destroys an event's object, such as the `Class.Player` object when a user leaves the experience, all of its [non-deferred](deferred.md) connections disconnect automatically.
</Alert>
## Wait for events to fire
If you want a script to yield until a specific event fires, use the `Datatype.RBXScriptSignal:Wait()|Wait()` method. This method returns the event's arguments, which you can assign to variables for later use:
```lua
local Workspace = game:GetService("Workspace")
local part = Workspace.Part
local touchedPart = part.Touched:Wait()
print("The part was touched by", touchedPart:GetFullName())
```
## Other types of events
- [Bindable events](bindable.md) let you communicate between scripts on the **same side** of the client-server boundary.
- [Remote events](remote.md) let you communicate **across** the client-server boundary.
- [Deferred engine events](deferred.md) defer event handlers until certain resumption points.

View file

@ -0,0 +1,584 @@
---
title: Remote events and callbacks
description: Remote network events and callbacks allow for back-and-forth communication across the client-server boundary.
---
Roblox experiences are multiplayer by default, so all experiences 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 experience'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](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 an experience. 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 experience's state changes, server scripts can use remote events to notify clients of changes to scores, objectives, etc.
- **In-experience Marketplace purchases** - For an example implementation that uses remote functions, see [Prompt subscription purchases](../../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.
<Tabs>
<TabItem label="Remote events">
<table>
<thead>
<tr><td colspan="2">[Client &rarr; Server](#client-server)</td></tr>
</thead>
<tbody>
<tr>
<td width="12%">Client</td>
<td>`RemoteEvent:FireServer(args)`</td>
</tr>
<tr>
<td>Server</td>
<td>`RemoteEvent.OnServerEvent:Connect(function(player, args))`</td>
</tr>
</tbody>
<thead>
<tr><td colspan="2">[Server &rarr; Client](#server-client)</td></tr>
</thead>
<tbody>
<tr>
<td width="12%">Server</td>
<td>`RemoteEvent:FireClient(player, args)`</td>
</tr>
<tr>
<td>Client</td>
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
</tr>
</tbody>
<thead>
<tr><td colspan="2">[Server &rarr; All Clients](#server-all-clients)</td></tr>
</thead>
<tbody>
<tr>
<td width="12%">Server</td>
<td>`RemoteEvent:FireAllClients(args)`</td>
</tr>
<tr>
<td>Client</td>
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
</tr>
</tbody>
</table>
</TabItem>
<TabItem label="Remote functions">
<table>
<thead>
<tr><td colspan="2">[Client &rarr; Server &rarr; Client](#client-server-client)</td></tr>
</thead>
<tbody>
<tr>
<td width="12%">Client</td>
<td>`serverResponse = RemoteFunction:InvokeServer(args)`</td>
</tr>
<tr>
<td>Server</td>
<td>`RemoteFunction.OnServerInvoke = function(player, args)`</td>
</tr>
</tbody>
<thead>
<tr><td colspan="2">[Server &rarr; Client &rarr; Server](#server-client-server)</td></tr>
</thead>
<tbody>
<tr>
<td colspan="2">See [risks](#server-client-server).</td>
</tr>
</tbody>
</table>
</TabItem>
</Tabs>
## 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](../../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`.
1. Click the **&CirclePlus;** button that appears to the right of the container's name and insert a **RemoteEvent** instance.
1. 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).
<GridContainer numColumns="3">
<figure>
<center>
<img src="../../assets/scripting/client-server/Remote-Flow-Client-Server.png" width="85%" />
<figcaption>Client &rarr; Server</figcaption>
</center>
</figure>
<figure>
<center>
<img src="../../assets/scripting/client-server/Remote-Flow-Server-Client.png" width="85%" />
<figcaption>Server &rarr; Client</figcaption>
</center>
</figure>
<figure>
<center>
<img src="../../assets/scripting/client-server/Remote-Flow-Server-All-Clients.png" width="85%" />
<figcaption>Server &rarr; All Clients</figcaption>
</center>
</figure>
</GridContainer>
<Alert severity="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`.
</Alert>
### Client&nbsp;→ server
You can use a `Class.LocalScript` to trigger an event on the [server](../../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.
<table size="small">
<tr>
<td width="12%">Client</td>
<td>`RemoteEvent:FireServer(args)`</td>
</tr>
<tr>
<td>Server</td>
<td>`RemoteEvent.OnServerEvent:Connect(function(player, args))`</td>
</tr>
</table>
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 title="Event Connection - Script"
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 title="Event Firing - LocalScript"
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&nbsp;→ client
You can use a `Class.Script` to trigger an event on a [client](../../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`.
<table size="small">
<tr>
<td width="12%">Server</td>
<td>`RemoteEvent:FireClient(player, args)`</td>
</tr>
<tr>
<td>Client</td>
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
</tr>
</table>
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 title="Event Connection - LocalScript"
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 title="Event Firing - Script"
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&nbsp;→ 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.
<table size="small">
<tr>
<td width="12%">Server</td>
<td>`RemoteEvent:FireAllClients(args)`</td>
</tr>
<tr>
<td>Client</td>
<td>`RemoteEvent.OnClientEvent:Connect(function(args))`</td>
</tr>
</table>
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 title="Event Connection - LocalScript"
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 title="Event Firing - Script"
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](../../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`.
1. Click the **&CirclePlus;** button that appears to the right of the container's name and insert a **RemoteFunction** instance.
1. 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).
<GridContainer numColumns="3">
<figure>
<center>
<img src="../../assets/scripting/client-server/Remote-Flow-Client-Server-Client.png" width="85%" />
<figcaption>Client &rarr; Server &rarr; Client</figcaption>
</center>
</figure>
<figure>
<center>
<img src="../../assets/scripting/client-server/Remote-Flow-Server-Client-Server.png" width="85%" />
<figcaption>Server &rarr; Client &rarr; Server</figcaption>
</center>
</figure>
</GridContainer>
### Client&nbsp;→ server&nbsp;→ client
You can use a `Class.LocalScript` to call a function on the [server](../../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.
<table size="small">
<tr>
<td width="10%">Client</td>
<td>`RemoteFunction:InvokeServer(args)`</td>
</tr>
<tr>
<td>Server</td>
<td>`RemoteFunction.OnServerInvoke = function(player, args)`</td>
</tr>
</table>
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 title="Callback Connection - Script"
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 title="Event Invocation - LocalScript"
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&nbsp;→ client&nbsp;→ 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](../../luau/userdata.md), or [function](../../luau/functions.md), Roblox automatically converts those indices to strings.
```lua title="Event Connection - LocalScript"
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 title="Event Firing - Script"
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](../../projects/client-server.md) boundary, making it impossible to pass functions remotely. Instead, the resulting argument on the receiving side will be `nil`.
```lua title="Event Connection - LocalScript"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:FindFirstChildOfClass("RemoteEvent")
local function onClientEvent(func)
print(func) --> nil
end
remoteEvent.OnClientEvent:Connect(onClientEvent)
```
```lua title="Event Firing - Script"
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.
<Alert severity="warning">
Whether passing a dictionary table **or** a numerically indexed table, avoid `nil` values for any index.
</Alert>
```lua title="Event Connection - Script"
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 title="Event Firing - LocalScript"
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 title="Callback Connection - Script"
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 title="Event Invocation - LocalScript"
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 title="Event Connection - Script"
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 title="Event Firing - LocalScript"
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 title="Event Firing - Script"
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 title="Event Firing - LocalScript"
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)
```

View file

@ -0,0 +1,153 @@
---
title: Scripting
description: An introduction to scripting in Roblox with the Luau programming language.
---
Scripts are plain text files that let you add custom, dynamic behavior to your experiences. You can use scripts to trigger in-game events, respond to player input, save player data, create leaderboards, spawn enemies, control NPC behavior, and much, much more.
<Alert severity="success">
This section is for creators with some coding experience who want to know the specifics of scripting in Roblox.
If you've never written code before and want an introduction to programming, see [Coding fundamentals](../tutorials/fundamentals/coding-1/coding-fundamentals.md), which covers concepts like variables, functions, conditionals, loops, and arrays. For a more guided, step-by-step approach, see the [Basic gameplay](../tutorials/use-case-tutorials/scripting/basic-scripting/intro-to-scripting.md) tutorial.
</Alert>
## Luau
Roblox scripts use the [Luau](https://luau.org) programming language, which is derived from [Lua 5.1](https://www.lua.org/manual/5.1/).
- Compared to Lua 5.1, Luau adds performance enhancements and many useful features, including an optional typing system, string interpolation, and generalized iteration for tables.
- All valid Lua 5.1 code is valid Luau code, but the opposite is not true.
Most books and online resources for Lua are still broadly applicable to Luau. For a detailed summary of differences, see [Compatibility](https://luau.org/compatibility) in the Luau documentation. For language syntax, see the [Luau reference](../luau/index.md).
### Luau basics
Luau is gradually typed, so you don't need to specify a type when you create a variable. You can use `Global.LuaGlobals.type()` to check object type:
```lua
logMessage = "User has more than 10 items!"
print(logMessage) --> User has more than 10 items!
print(type(logMessage)) --> string
```
Luau has global and local [scopes](../luau/scope.md), but it's almost always better to declare variables and functions locally with the `local` keyword:
```lua
local logMessage = "User has more than 10 items!"
local function printMessage()
print(logMessage)
end
printMessage() --> User has more than 10 items!
```
Luau uses `nil` to represent nonexistence or nothingness, which evaluates as `false` in conditional statements:
```lua
local messageToUser
print(messageToUser) --> nil
print(type(message)) --> nil
if messageToUser then
-- statement evaluates to false
end
```
As you might have noticed, `--` starts a one-line comment. `--[[]]` creates a block comment:
```lua
--[[
Shuts off the cosmic moon ray immediately.
Should only be called within 15 minutes of midnight Mountain Standard
Time to avoid damage to the cosmic moon ray.
]]
local function stopCosmicMoonRay()
-- add this later, it might prove important
end
```
[Tables](../luau/tables.md) are the generic term for arrays and dictionaries. Arrays are one-based rather than zero-based, so the first item is `[1]`. You declare arrays and dictionaries with a single set of curly braces:
```lua
local myArray = {"chips", "sparkling water", "salsa"}
local myDictionary = {
snack = "chips",
drink = "sparkling water",
dip = "salsa"
}
print(myArray[1]) --> chips
print(myDictionary.dip) --> salsa
```
You can iterate over tables using `for` loops with the `Global.LuaGlobals.ipairs()` function for arrays and the `Global.LuaGlobals.pairs()` function for dictionaries, but Luau also lets you omit these functions for cleaner syntax:
```lua
for index, value in ipairs(myArray) do -- standard Lua
print(index, value)
end
for key, value in pairs(myDictionary) do -- standard Lua
print(key, value)
end
for key, value in myDictionary do -- Luau generalized iteration
print(key, value)
end
```
## Your first script
1. In Roblox Studio, hover over **ServerScriptService** in the [Explorer](../studio/explorer.md) window and click **+**.
1. Select **Script** to add a new script.
1. Right-click the script and rename it to `HelloScript`.
1. Double-click the script to open it in the [Script Editor](../studio/script-editor.md).
1. Add the following code to the file:
```lua
local helloArray = {"h", "e", "l", "l", "o"}
local worldArray = {"w", "o", "r", "l", "d"}
for index, value in helloArray do
print(value)
end
print(table.concat(worldArray))
```
1. Ensure that the [Output](../studio/output.md) window is open.
1. [Initiate a playtest](../studio/testing-modes.md#playtesting) to run your experience and note the output:
```text
h
e
l (x2)
o
world
```
## Get comfortable
A big part of adapting to a new development environment is configuring it to meet your needs and understanding the tools at your disposal:
- The **Script Editor** section of [Studio Settings](../studio/setup.md#customization) lets you adjust quality of life features like font, colors, indentation, autocomplete, brackets, and tooltips. You might also want to enable dark mode in the **Studio** section.
- Holding <kbd>Ctrl</kbd> or <kbd>Command</kbd> and clicking on a function or variable takes you to its declaration in your codebase (or its online documentation). Using [Find and Find All](../studio/script-editor.md#find-and-replace) can help you navigate larger projects.
- The [Output](../studio/output.md) window is the most basic tool for understanding the behavior of your scripts. Use the **&#x22EF;** menu to enable **Show Context** and **Show Source**.
- The [Script Analysis](../studio/script-editor.md#script-analysis) window shows a summary of errors and warnings, but you might find its utility limited; the Script Editor already highlights these issues as you type.
- Logging capabilities are minimal, with no concept of log levels like `DEBUG` or `FATAL`. Use `Global.LuaGlobals.print()` and `Global.RobloxGlobals.warn()`.
For more information about configuring Studio for scripting, see [Script Editor](../studio/script-editor.md).
<Alert severity="success">
To use your favorite text editor and version control system rather than the built-in editor, see [Script Sync](./sync.md).
</Alert>
## Your second script
1. In Roblox Studio, add a script to **ReplicatedStorage** in the [Explorer](../studio/explorer.md) window and rename it to `OhNo`.
1. Add the following code to the file:
```lua
print("Hello script types and locations!")
```
1. [Initiate a playtest](../studio/testing-modes.md#playtesting) to run your experience.
1. Note how the output is no different than when you ran [your first script](#your-first-script).
To understand why the script didn't run, see [Script Types and Locations](./locations.md).

View file

@ -0,0 +1,51 @@
---
title: Script types and locations
description: How scripts run in Roblox, and how location impacts that behavior.
---
import ScriptLocations from '../includes/engine-comparisons/script-locations.md'
For many developers, the fundamental challenge of adapting to Roblox scripting is the importance of file location and the `Class.Script.RunContext` property. Depending on script type, location in the **Explorer**, and run context, scripts can behave very differently. Certain method calls might fail, objects in your experience might be inaccessible, or scripts might not run at all.
The reason for this complexity is that Roblox experiences are multiplayer by default. Scripts need the ability to only run on the server, only run on the client, or be shared across both. The evolution of the Roblox platform over time has further complicated the situation.
## Script types
Roblox has three types of scripts:
- `Class.Script` - Code that runs on either the server or the client, depending on its location and `Class.Script.RunContext` property.
- `Class.LocalScript` - Code that runs only on the client. Does not have a run context.
- `Class.ModuleScript` - Code that you can reuse in other scripts. Does not have a run context.
When you create a `Class.Script`, its default run context is `Legacy`, meaning that it a) is a server-side script and b) only runs if it is in a server container, such as `Class.Workspace` or `Class.ServerScriptService`.
- If you change the script's run context to `Server`, it can now also run in `Class.ReplicatedStorage`, but that's not recommended. The contents of that location are replicated to clients, so it's a poor location for server-side scripts.
- If you change the script's run context to `Client`, it can run in `ReplicatedStorage`. It can also run in `Class.StarterCharacterScripts` and `Class.StarterPlayerScripts`. Starter containers are copied to clients, though, so the original script **and** the copy run, which isn't desirable.
To change a script run context, select it in the [Explorer](../studio/explorer.md) and change the value in the [Properties](../studio/properties.md) window.
<img src="../assets/studio/properties/Script-RunContext.png" alt="RunContext property indicated for a Script." width="320 "/>
### Recommendations
- Using `Server` and `Client` values for `RunContext` removes ambiguity from how scripts run and can help keep your project organized. The best use cases for non-default `RunContext` values are:
- Client scripts that you want to run from `Class.ReplicatedStorage` or `Class.ReplicatedFirst`.
- Server or client scripts that you include within [models](../parts/models.md) and [packages](../projects/assets/packages.md). Explicitly setting the run context makes models and packages more likely to work properly from a variety of locations.
- To share code between server and client scripts, use `Class.ModuleScript|ModuleScripts` in `ReplicatedStorage`.
- Use `Class.LocalScript|LocalScripts` in `StarterCharacterScripts`, `StarterPlayerScripts`, `StarterGui`, and `StarterPack`.
## Script locations
<ScriptLocations components={props.components} />
This image shows which **Explorer** window locations can contain client scripts. Remember, `ReplicatedFirst` and `ReplicatedStorage` can contain `Class.Script|Scripts` with a `Class.BaseScript.RunContext|RunContext` of `Enum.RunContext|Client`, whereas the `Starter[]` containers should use `Class.LocalScript|LocalScripts`.
<img alt="Diagram showing which script locations run on clients." src="../assets/scripting/client-server/Client-Script-Containers.png" width="520" />
## Example project structure
The [Plant](../resources/plant-reference-project.md) reference project shows how you might organize your code in a large, complex experience.
Of particular note is how it stores the vast majority of its code as reusable `Class.ModuleScript|ModuleScripts` in `ReplicatedStorage` and `ServerStorage`.

View file

@ -0,0 +1,247 @@
---
title: Reuse code
description: How to use module scripts to reuse code.
---
After creating a few scripts, it's never long before you want to reuse some code between them. Depending on [location](./locations.md), `Class.ModuleScript|ModuleScripts` let you reuse code between scripts on different sides of the client-server boundary or the same side of the boundary.
You can put module scripts anywhere that you put scripts, but `Class.ReplicatedStorage` is a popular location; storing module scripts here lets you reuse code between the server and clients.
## Anatomy of a module script
In Roblox Studio, add a module script to **ReplicatedStorage** and rename it to `PickupManager`. Each `Class.ModuleScript` starts with the following code:
```lua
local module = {}
return module
```
This code creates an empty Luau [table](../luau/tables.md) and returns it to any script that requires the module script.
The return value can be any [data type](/reference/engine/datatypes) except for `nil`, but most module scripts return a function, a table, or a table of functions. To generate its return value, module scripts can of course run arbitrary code, which includes requiring other module scripts.
<Alert severity="info">
Be careful not to have module scripts require each other in a circular manner, which results in a `Requested module was required recursively` error.
</Alert>
The following example returns a table with a single function called `getPickupBonus`. Paste it into the new module script:
```lua
-- ModuleScript in ReplicatedStorage
local PickupManager = {}
local defaultMultiplier = 1.25
local rarityMultipliers = {
common = 10,
uncommon = 20,
rare = 50,
legendary = 100
}
-- Add the getPickupBonus function to the PickupManager table
PickupManager.getPickupBonus = function(rarity)
local bonus = rarityMultipliers[rarity] * defaultMultiplier
return bonus
end
return PickupManager
```
Adding the function to a table isn't strictly necessary—you could just return the function itself—but it's a good pattern to follow; it gives you an easy-to-understand syntax when you call the function from another script and lets you easily add more functions to the module script over time.
## Require module scripts
To load a module script, you call the `Global.LuaGlobals.require()` function. In `ReplicatedStorage`, add a new script, and change its `RunContext` to `Client`. Then add the following code to call the `PickupManager.getPickupBonus` function:
```lua title="Client script in ReplicatedStorage"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get the value returned by the ModuleScript
local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
-- Call a ModuleScript function
local bonus = PickupManager.getPickupBonus("legendary")
print(bonus) --> 125
```
<Alert severity="info">
The `Class.Instance:WaitForChild()` pattern is an important safety measure in client scripts. For more information, see [Replication order](attributes.md#replication-order).
</Alert>
Storing module scripts in `ReplicatedStorage` lets you share code between the server and client, so you can use the same code to require the script from `ServerScriptService`:
```lua title="Script in ServerScriptService"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
```
When you call `Global.LuaGlobals.require()` on a `Class.ModuleScript`, it runs once and returns a single item as a reference. Calling `Global.LuaGlobals.require()` again returns the exact same reference, meaning that if you modify a returned [table](../luau/tables.md) or `Class.Instance`, subsequent `Global.LuaGlobals.require()` calls return that modified reference. The module itself doesn't run multiple times.
If you require a `Class.ModuleScript` from both sides of the client-server boundary, as in the example above, the `Class.ModuleScript` returns a unique reference for each side.
## Patterns
Module scripts have some common patterns that you can use to simplify your code and avoid pitfalls as your experience grows in size and complexity.
<Alert severity="success">
Most of these patterns require an understanding of events. If you're not familiar with them, see [Events](events.md).
</Alert>
### Data sharing
To associate data with individual objects, you can assign attributes to them or create `Class.Configuration` folders with value objects such as `Class.StringValue` or `Class.IntValue`. However, both approaches are troublesome if you want to add or modify dozens of objects or data values. They also don't store tables or functions.
If you want to modify the same data for multiple copies of the same
object or reuse the same data for different objects, store the data in
`Class.ModuleScript|ModuleScripts`. It's an easier way for you to reuse the data in other scripts, and you can store tables and functions.
The following example `Class.ModuleScript` in `Class.ReplicatedStorage|ReplicatedStorage` stores the configuration values for a generic gun:
```lua title="ModuleScript in ReplicatedStorage"
local GunConfig = {}
GunConfig.MagazineSize = 20
GunConfig.AmmoCount = 100
GunConfig.Firerate = 600
GunConfig.Damage = {
["Head"] = 50;
["Torso"] = 40;
["Body"] = 25;
}
return GunConfig
```
### Custom events
Custom events enable scripts to communicate with each other, but having to keep track of references to individual `Class.BindableEvent` objects can clutter your code.
You can use `Class.ModuleScript|ModuleScripts` to store
`Class.BindableEvent|BindableEvents` and provide
custom event handlers that are directly tied to the methods of `Class.ModuleScript`.
The following `Class.ModuleScript` in `Class.ReplicatedStorage|ReplicatedStorage` has a custom event that fires when the switch changes state:
```lua title="ModuleScript in ReplicatedStorage"
local Switch = {}
-- Creating bindable so any script can listen to when the switch was changed
local bindableEvent = Instance.new("BindableEvent")
Switch.Changed = bindableEvent.Event
local state = false
function Switch.flip()
state = not state
bindableEvent:Fire(state)
end
return Switch
```
The following client script in `Class.ReplicatedStorage` connects a function to call when the `Switch.Changed` event fires.
```lua title="Script in ReplicatedStorage"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Switch = require(ReplicatedStorage:WaitForChild("Switch"))
Switch.Changed:Connect(function(newState)
print("Switch state is now", newState)
end)
-- Test the flipping a few times
task.wait(1)
Switch.flip()
task.wait(1)
Switch.flip()
```
### Encapsulation
Encapsulation is the practice of creating a layer of abstraction around objects or scripting logic to hide complexity. You can use `Class.ModuleScript|ModuleScripts` to encapsulate Roblox objects with custom Luau functions to simplify code.
For example, you can use encapsulation to:
- Simplify cross-network communication with a single `Class.RemoteEvent` object.
- Wrap error handling code around sensitive services such as `Class.DataStoreService`.
- Define custom methods to control or extend Roblox object features.
It's difficult to keep track of dozens of individual `Class.RemoteEvent` objects to implement networking in your experience. You can use a `Class.ModuleScript` to encapsulate a single `Class.RemoteEvent` to help simplify this problem. By including a unique `id` argument, you can still send different network messages while only using a single `Class.RemoteEvent`.
In the example below, the `Class.ModuleScript` named `NetworkManagerClient` encapsulates the `Class.RemoteEvent:FireServer()` method to include this extra `id` argument. Additionally, this `Class.ModuleScript` references the `Class.RemoteEvent` object itself so you don't have to reference it in other parts of your code. You only need to require this `Class.ModuleScript` to send network messages and don't need to deal with `Class.RemoteEvent` objects in the rest of your codebase.
The following `Class.ModuleScript` in `Class.ReplicatedFirst` provides an encapsulated function that you can call on your client scripts to send a network message:
```lua title="Network Module"
-- ModuleScript in ReplicatedFirst named NetworkManagerClient
local NetworkManagerClient = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Encapsulating the remote object's FireServer function
function NetworkManagerClient.FireServer(id, ...)
remoteEvent:FireServer(id, ...)
end
return NetworkManagerClient
```
The following `Class.ModuleScript` in `Class.ServerScriptService` uses `Class.BindableEvent|BindableEvents` for every script to connect to a specific ID. When a client sends a network message, each `Class.BindableEvent` associated with the specified ID fires.
```lua
-- ModuleScript in ServerScriptService named NetworkManagerServer
local NetworkManagerServer = {}
local networkSignalList = {}
function NetworkManagerServer.GetServerEventSignal(id)
local bindableEvent = Instance.new("BindableEvent")
-- Linking the new BindableEvent to the id
table.insert(networkSignalList, {
id = id,
bindableEvent = bindableEvent,
})
return bindableEvent.Event
end
-- Connecting to
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
remoteEvent.OnServerEvent:Connect(function(player, id, ...)
-- Finding every bindable event that matches the ID of the received remote event
for _, signal in networkSignalList do
if signal.id == id then
signal.bindableEvent:Fire(player, ...)
end
end
end)
return NetworkManagerServer
```
The following `Class.LocalScript` sends a message with the ID `RequestA` with an optional `Hello` argument.
```lua
-- LocalScript in ReplicatedFirst
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local NetworkManagerClient = require(ReplicatedFirst:WaitForChild("NetworkManagerClient"))
NetworkManagerClient.FireServer("RequestA", "Hello")
```
The following `Class.Script` connects to the network message ID `RequestA` and prints out a statement with any additional parameters when it receives the request.
```lua
-- Script in ServerScriptService
local ServerScriptService = game:GetService("ServerScriptService")
local NetworkManagerServer = require(ServerScriptService:WaitForChild("NetworkManagerServer"))
NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)
print("Received RequestA from", player, ...)
end)
```

View file

@ -0,0 +1,333 @@
---
title: Parallel Luau
description: Parallel Luau runs code on multiple threads simultaneously.
---
With the **Parallel Luau** programming model, you can run code on multiple threads simultaneously, which can improve the performance of your experience. As you expand your experience with more content, you can adopt this model to help maintain the performance and safety of your Luau scripts.
<video controls width="100%" src="../assets/scripting/scripts/Parallel-Luau.mp4"></video>
## Parallel programming model
By default, scripts execute sequentially. If your experience has complex logic or content, such as non-player characters (NPCs), raycasting validation, and procedural generation, then sequential execution might cause lag for your users. With the parallel programming model, you can [split tasks into multiple scripts](#split-code-into-multiple-threads) and run them in parallel. This makes your experience code run faster, which improves the user experience.
The parallel programming model also adds safety benefits to your code. By splitting code into multiple threads, when you edit code in one thread, it doesn't affect other code running in parallel. This reduces the risk of having one bug in your code corrupting the entire experience, and minimizes the delay for users in live servers when you push an update.
Adopting the parallel programming model doesn't mean to put everything in multiple threads. For example, the [server-side raycasting validation](#server-side-raycasting-validation) sets each individual user a remote event in parallel but still requires the initial code to run serially to change global properties, which is a common pattern for parallel execution.
Most times you need to combine serial and parallel phases to achieve your desired output, since currently there are some operations not supported in parallel that can prevent scripts from running, such as modifying instances in parallel phases. For more information on the level of usage of APIs in parallel, see [thread safety](#thread-safety).
## Split code into multiple threads
To run your experience's scripts in multiple threads concurrently, you need to split them into logical chunks under different **actors** in the [data model](../projects/data-model.md). Actors are represented by `Class.Actor` instances inheriting from `Class.DataModel`. They work as units of execution isolation that distribute the load across multiple cores running simultaneously.
### Place actor instances
You can put actors in proper containers or use them to replace the top-level instance types of your 3D entities such as NPCs and raycasters, then add corresponding scripts.
<img alt="An example of a Script under an Actor" src="../assets/studio/explorer/ServerScriptService-Actor.png" width="320" />
For most situations, you shouldn't put an actor as a child of another actor in the data model. However, if you decide to place a script nested within multiple actors for your specific use case, the script is owned by its closest ancestor actor.
<img alt="A tree of actors and scripts that shows how a script is owned by its closest actor" src="../assets/scripting/scripts/Actor-Tree.png" width="320" />
### Desynchronize threads
Though putting scripts under actors grants them the capability for parallel execution, by default the code still runs on the single thread serially, which doesn't improve the runtime performance. You need to call `Library.task.desynchronize()`, a yieldable function that suspends the execution of the current coroutine for running code in parallel and resumes it at the next parallel execution opportunity. To switch a script back to serial execution, call `Library.task.synchronize()`.
Alternatively, you can use `Datatype.RBXScriptSignal:ConnectParallel()` method when you want to schedule a signal callback to immediately run your code in parallel upon triggering. You don't need to call `Library.task.desynchronize()` inside the signal callback.
```lua title="Desynchronize a Thread"
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Some parallel code that computes a state update
task.synchronize()
... -- Some serial code that changes the state of instances
end)
```
<Alert severity="warning">
You can't use `require()` in a desynchronized parallel phase. Require scripts you want to use first in a serial context.
</Alert>
Scripts that are part of the same actor always execute sequentially with respect to each other, so you need multiple actors. For example, if you put all parallel-enabled behavior scripts for your NPC in one actor, they still run serially on a single thread, but if you have multiple actors for different NPC logic, each of them runs in parallel on its own thread. For more information, see [Best practices](#best-practices).
<GridContainer numColumns="2">
<figure>
<img src="../assets/scripting/scripts/single-thread.png" />
<figcaption>Parallel code in Actors running serially in a single thread</figcaption>
</figure>
<figure>
<img src="../assets/scripting/scripts/multi-thread.png" />
<figcaption>Parallel code in Actors running simultaneously in multiple threads</figcaption>
</figure>
</GridContainer>
### Thread safety
During the parallel execution, you can access most instances of the `Class.DataModel` hierarchy as usual, but some API properties and functions aren't safe to read or write. If you use them in your parallel code, the Roblox engine can automatically detect and prevent these accesses from occurring.
API members have a thread safety level that indicates whether and how you can use them in your parallel code, as the following table shows:
<table>
<thead>
<tr>
<th>Safety level</th>
<th>For properties</th>
<th>For functions</th>
</tr>
</thead>
<tbody>
<tr>
<td>**Unsafe**</td>
<td>Cannot be read or written in parallel.</td>
<td>Cannot be called in parallel.</td>
</tr>
<tr>
<td>**Read Parallel**</td>
<td>Can be read but not written in parallel.</td>
<td>N/A</td>
</tr>
<tr>
<td>**Local Safe**</td>
<td>Can be used within the same Actor; can be read but not written to by other `Class.Actor|Actors` in parallel.</td>
<td>Can be called within the same Actor; cannot be called by other `Class.Actor|Actors` in parallel.</td>
</tr>
<tr>
<td>**Safe**</td>
<td>Can be read and written.</td>
<td>Can be called.</td>
</tr>
</tbody>
</table>
You can find thread safety tags for API members on the [API reference](/reference/engine). When using them, you should also consider how API calls or property changes might interact between parallel threads. Usually it's safe for multiple actors to read the same data as other actors but not modify the state of other actors.
<Alert severity="info">
If an API member doesn't specify a thread safety level, by default its thread safety level is **Unsafe**.
</Alert>
## Cross-thread communication
Under the multithreading context, you can still allow scripts in different actors to communicate with each other to exchange data, coordinate tasks, and synchronize activities. The engine supports the following mechanisms for cross-thread communication:
- [Actor messaging](#actor-messaging) API for sending messages to an actor using scripts.
- [Shared table](#shared-table) data structure for efficiently sharing a large amount of data between multiple actors on a shared state.
- [Direct data model communication](#direct-data-model-communication) for simple communication with restrictions.
You can support multiple mechanisms to accommodate your cross-thread communication needs. For example, you can send a shared table through the Actor Messaging API.
### Actor messaging
The **actor messaging** API allows a script, either in a serial or parallel context, to send data to an actor in the same data model. Communication through this API is asynchronous, in which the sender doesn't block until the receiver receives the message.
When sending messages using this API, you need to define a **topic** for categorizing the message. Each message can only be sent to a single actor, but that actor can internally have multiple callbacks bound to a message. Only scripts that are descendants of an actor can receive messages.
The API has the following methods:
- `Class.Actor:SendMessage()` for sending a message to an actor.
- `Class.Actor:BindToMessage()` for binding a Luau callback to a message with the specified topic in a serial context.
- `Class.Actor:BindToMessageParallel()` for binding a Luau callback to a message with the specified topic in a parallel context.
The following example shows how to use `Class.Actor:SendMessage()` to define a topic and send a message on the sender's end:
```lua title="Example Message Sender"
local Workspace = game:GetService("Workspace")
-- Send two messages to the worker actor with a topic of "Greeting"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")
```
The following example shows how to use `Class.Actor:BindToMessageParallel()` to bind a callback for certain topic in a parallel context on the receiver's end:
```lua title="Example Message Receiver"
-- Get the actor this script is parented to
local actor = script:GetActor()
-- Bind a callback for the "Greeting" message topic
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
```
### Shared table
`Datatype.SharedTable` is a table-like data structure accessible from scripts running under multiple actors. It's useful for situations that involve a large amount of data and require a common shared state between multiple threads. For example, when multiple actors work on a common world state that is not stored in the data model.
Sending a shared table to another actor doesn't make a copy of the data. Instead, shared tables allow safe and atomic updates by multiple scripts simultaneously. Every update to a shared table by one actor is immediately visible to all actors. Shared tables can also be cloned in a resource-efficient process that utilizes structural sharing instead of copying the underlying data.
### Direct data model communication
You can also facilitate communication between multiple threads directly using the data model, in which different actors can write and subsequently read properties or attributes. However, to maintain the thread-safety, scripts running in parallel generally can't write to the data model. So directly using the data model for communication comes with restrictions and may force scripts to synchronize frequently, which can impact performance of your scripts.
## Examples
### Server-side raycasting validation
For a fighting and battle experience, you need to enable [raycasting](../workspace/raycasting.md) for your users' weapons. With the client simulating the weapons to achieve good latency, the server has to confirm the hit, which involves doing raycasts and some amount of heuristics that compute expected character velocity, and look at past behavior.
Instead of using a single centralized script that connects to a remote event that clients use to communicate hit information, you can run each hit validation process on the server side in parallel with every user character having a separate remote event.
The server-side script that runs under that character's `Class.Actor` connects to this remote event using a parallel connection to run the relevant logic for confirming the hit. If the logic finds a confirmation of a hit, the damage is deducted, which involves changing properties, so it runs serially initially.
```lua
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Create new remote event and parent it to the tool
remoteEvent.Name = "RemoteMouseEvent" -- Rename it so that the local script can look for it
remoteEvent.Parent = tool
local remoteEventConnection -- Create a reference for the remote event connection
-- Function which listens for a remote event
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Execute setup code in serial
local character = player.Character
-- Ignore the user's character while raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Perform the raycast in parallel
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Used to extend the ray slightly since the click location might be slightly offset from the object
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = Workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- SERIAL: The code below modifies state outside of the actor
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Make the explosion non-deadly
explosion.Position = clickLocation.Position
-- Multiple actors could get the same part in a raycast and decide to destroy it
-- This is perfectly safe but it would result in two explosions at once instead of one
-- The following double checks that execution got to this part first
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Destroy it
end
end
end
end
-- Connect the signal in serial initially since some setup code is not able to run in parallel
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
```
### Server-side procedural terrain generation
To create a vast world for your experience, you can populate the world dynamically. Procedural generation typically creates independent terrain chunks, with the generator performing relatively intricate calculations for object placement, material usage, and voxel filling. Running generation code in parallel can enhance efficiency of the process. The following code sample serves as an example.
```lua
-- Parallel execution requires the use of actors
-- This script clones itself; the original initiates the process, while the clones act as workers
local Workspace = game:GetService("Workspace")
local actor = script:GetActor()
if actor == nil then
local workers = {}
for i = 1, 32 do
local actor = Instance.new("Actor")
script:Clone().Parent = actor
table.insert(workers, actor)
end
-- Parent all actors under self
for _, actor in workers do
actor.Parent = script
end
-- Instruct the actors to generate terrain by sending messages
-- In this example, actors are chosen randomly
task.defer(function()
local rand = Random.new()
local seed = rand:NextNumber()
local sz = 10
for x = -sz, sz do
for y = -sz, sz do
for z = -sz, sz do
workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
end
end
end
end)
-- Exit from the original script; the rest of the code runs in each actor
return
end
function makeNdArray(numDim, size, elemValue)
if numDim == 0 then
return elemValue
end
local result = {}
for i = 1, size do
result[i] = makeNdArray(numDim - 1, size, elemValue)
end
return result
end
function generateVoxelsWithSeed(xd, yd, zd, seed)
local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
local occupancy = makeNdArray(3, 4, 1)
local rand = Random.new()
for x = 0, 3 do
for y = 0, 3 do
for z = 0, 3 do
occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
end
end
end
return {materials = materials, occupancy = occupancy}
end
-- Bind the callback to be called in parallel execution context
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
local voxels = generateVoxelsWithSeed(x, y, z, seed)
local corner = Vector3.new(x * 16, y * 16, z * 16)
-- Currently, WriteVoxels() must be called in the serial phase
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
```
## Best practices
To apply the maximum benefits of parallel programming, refer to the following best practices when adding your Luau code:
- **Avoid Long Computations** — Even in parallel, long computations can block execution of other scripts and cause lag. Avoid using parallel programming to handle a large volume of long, unyielding calculations.
<img src="../assets/scripting/scripts/ParallelExecutionDark.png" width="100%" alt="Diagram demonstrating how overloading the parallel execution phase can still cause lag" />
- **Use the Right Number of Actors** — For the best performance, use more `Class.Actor|Actors`. Even if the device has fewer cores than `Class.Actor|Actors`, the granularity allows for more efficient load balancing between the cores.
<img src="../assets/scripting/scripts/FewerVsMoreActorsDark.png" width="100%" alt="Demonstration of how using more actors balances the load across cores" />
This doesn't mean you should use as many `Class.Actor|Actors` as possible. You should still divide code into `Class.Actor|Actors` based on logic units rather than breaking code with connected logic to different `Class.Actor|Actors`. For example, if you want to enable [raycasting validation](#server-side-raycasting-validation) in parallel, it's reasonable to use 64 `Class.Actor|Actors` and more instead of just 4, even if you're targeting 4-core systems. This is valuable for scalability of the system and allows it to distribute the work based on the capability of the underlying hardware. However, you also shouldn't use too many `Class.Actor|Actors`, which are hard to maintain.

View file

@ -0,0 +1,95 @@
---
title: Schedule code
description: Explains how to schedule code so it executes after a specific action or cycle has completed.
---
Scheduling code is useful in many situations, such as ensuring code executes after a specific action or cycle has completed, or delaying code for a specific duration of time. You can use the `Library.task` library to optimize Roblox's [task scheduler](../performance-optimization/microprofiler/task-scheduler.md) to manage and schedule code. You can also use a similar library called `Library.coroutine` to schedule code which has some additional functionality.
### Common methods
The following are the most common `Library.task` methods used to schedule code. You should use the task methods over legacy scheduling methods, such as `Global.RobloxGlobals.wait()`, to ensure that your code runs optimally.
<Alert severity="warning">
Certain legacy global methods, such as (`Global.RobloxGlobals.spawn()`, `Global.RobloxGlobals.delay()`, and `Global.RobloxGlobals.wait()`) can provide similar code scheduling results but are less optimized and configurable as their `Library.task` alternatives. If your experience uses these legacy methods, you should use `Library.task` instead to ensure your experience's code remains efficient and up-to-date.
</Alert>
The following table lists the relevant legacy global methods and their preferred, more optimized counterparts:
| Legacy global methods | Task methods | Additional alternatives |
| :-------------------------------------- | :------------------------------------------------- | :------------------------------------------------- |
| `wait()` | `Library.task.wait()` | `Class.RunService.Heartbeat` |
| `wait(n)` | `Library.task.wait()\|task.wait(n)` | |
| `spawn(f)` | `Library.task.defer()\|task.defer(f)` | `Library.task.delay()\|task.delay(0, f)` |
| `delay(n, f)` | `Library.task.delay()\|task.delay(n, f)` | |
| `spawn(function() f(uv1, ...) end)` | `Library.task.defer()\|task.defer(f, uv1, ...)` | `Library.task.delay()\|task.delay(0, f, uv1, ...)` |
| `delay(n, function() f(uv1, ...) end)` | `Library.task.delay()\|task.delay(n, f, uv1, ...)` | |
#### task.spawn()
`Library.task.spawn()` takes a thread or function and resumes it **immediately** through the engine's scheduler. Additional arguments are passed to the thread or function being resumed.
The following code sample is an example of how you can use `Library.task.spawn()` when calling a function that may yield while iterating over a set of objects:
```lua
local function playerAdded(player)
print(player)
end
for _, player in Players:GetPlayers() do
task.spawn(playerAdded, player)
end
```
#### task.defer()
`Library.task.defer()` takes a thread or function and defers it until the end of the current resume point within the current frame. Additional arguments are passed to the thread or function resuming.
You should typically use this when you want similar behavior to `Library.task.spawn()` but don't care about the thread running immediately. The following code sample illustrates how the `Global.RobloxGlobals.print()` statement for `"A"` will defer until after the `Global.RobloxGlobals.print()` statement for `"B"` executes:
```lua
task.defer(print, "A")
print("B")
--> B
--> A
```
<Alert severity="info">
`Library.task.defer()` is an optimized version of `Global.RobloxGlobals.spawn()` that schedules a thread to resume as soon as possible (but not immediately) without any throttling.
</Alert>
#### task.delay()
`Library.task.delay()` takes a thread or function and schedules it for resumption after the given amount of time elapses on the next `Class.RunService.Heartbeat|Heartbeat` step. The thread resumes with built-in error handling and support for other engine features. Any additional arguments are passed to the thread or function resuming.
Since the actual delay time may vary, the following code sample illustrates how you can calculate it by passing the current time as an argument:
```lua
task.delay(2, function(scheduledTime)
print(os.clock() - scheduledTime) --> 2.038702
end, os.clock())
```
A duration of zero will result in the thread or function resuming on the next step.
<Alert severity="info">
`Library.task.delay()` is an optimized version of `Global.RobloxGlobals.delay()` that schedules a thread to resume after some time elapses without throttling.
</Alert>
#### task.wait()
`Library.task.wait()` yields the current thread until the given duration (in seconds) elapses and then resumes the thread on the next `Class.RunService.Heartbeat|Heartbeat` step.
The actual yield time may vary. The following code sample illustrates how this method returns it for convenience:
Since the actual delay time may vary, the following code sample illustrates how you can get the actual time by storing the method's return value:
```lua
local elapsedTime = task.wait(2) -- Wait for 2 seconds
print(elapsedTime) --> 2.0792941
```
If no duration is given the duration will default to zero meaning the thread will automatically resume on the next step. This means `Library.task.wait()` is equivalent in behavior to `Class.RunService.Heartbeat`.
<Alert severity="info">
`Library.task.wait()` is an optimized version of `Global.RobloxGlobals.wait()` that schedules the current thread to resume after some time elapses without throttling.
</Alert>

View file

@ -0,0 +1,285 @@
---
title: Scripts
description: Scripts are containers that contain Luau code.
---
`Class.Script` objects are pieces of Luau code that can modify object behaviors and implement the overall logic of your experience. They can run on the client or server, depending on the functionality you're building. For example, a script that detects and acts on user input must run on the client, and a script that validates an in-experience consumable must run on the server.
Both server-side and client-side scripts can require `Class.ModuleScript` objects, which are [reusable modules](#module-scripts) for organizing and sharing code.
There are two things to consider when writing scripts:
Scripts have a `Class.Script.RunContext` property that sets whether the script runs on the client or server. There are three types of run context:
- **Legacy** - lets the script run based on its parent container. Legacy is the default run context.
- **Server** - lets the script run only on the server, regardless of its parent container.
- **Client** - lets the script run only on the client, regardless of its parent container.
Scripts need to reside in **script containers** in the data model. Based on the run context, you need to place the script in the right container. For more information, see the [server](../projects/data-model.md#server), [client](../projects/data-model.md#client), and [replication](../projects/data-model.md#replication) sections of the data model documentation.
<Alert severity="info">
You can also use `Class.LocalScript` objects for client-side scripts, but we recommend using regular scripts with the run context setting to specify whether the script runs on the client or server.
</Alert>
## Module scripts
`Class.ModuleScript` objects are reusable modules that script objects
load by calling the `Global.LuaGlobals.require()` function. Module scripts must return exactly one
value and run once and only once
per Luau environment. As a result, subsequent calls to `Global.LuaGlobals.require()` return a
cached value. You can execute arbitrary code in a `Class.ModuleScript`, but you only need to return what you need in other scripts.
Multiple scripts can require
the same module script, and one module script can be required by both
server-side scripts and client-side local scripts.
### Structure
Each `Class.ModuleScript` starts with the following code:
```lua
local module = {}
return module
```
- `local module = {}` creates an empty [table](../luau/tables.md).
- `return module` returns the table and its members to any script that imports the `Class.ModuleScript`.
The following example module script returns a `getPickupBonus` function in the `PickupManager` table:
```lua title="Example Module Script"
-- ModuleScript in ReplicatedStorage
local PickupManager = {}
local defaultMultiplier = 1.25
local rarityMultipliers = {
common = 10,
uncommon = 20,
rare = 50,
legendary = 100
}
-- Add the getPickupBonus function to the PickupManager module table
function PickupManager.getPickupBonus(rarity)
local bonus = rarityMultipliers[rarity] * defaultMultiplier
return bonus
end
return PickupManager
```
To call the `PickupManager.getPickupBonus` function:
```lua
-- Script in ReplicatedStorage
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get value returned by ModuleScript
local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
-- Call a ModuleScript function
local bonus = PickupManager.getPickupBonus("legendary")
print(bonus) --> 125
```
### Requiring
A `Class.ModuleScript` runs only when another script imports it using the `Global.LuaGlobals.require()` function. If a `Class.ModuleScript` requires another `Class.ModuleScript`, a `Class.Script` or `Class.LocalScript` must require the first `Class.ModuleScript` in the chain for any of them to run.
<Alert severity="warning">
Don't require `Class.ModuleScript|ModuleScripts` in a recursive or circular manner,
otherwise Studio throws an error: `Requested module was required recursively`.
</Alert>
To access a `Class.ModuleScript` from another script using the `Global.LuaGlobals.require()`
function:
```lua
-- Script in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Get the return value for the ModuleScript named "PickupManager"
local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
```
When you call `Global.LuaGlobals.require()` on a `Class.ModuleScript`, it runs **once** and returns a single item as a **reference**. Calling `Global.LuaGlobals.require()` again returns the exact same reference, meaning that if you modify a returned [table](../luau/tables.md) or `Class.Instance`, subsequent `Global.LuaGlobals.require()` calls return that modified reference. The module itself doesn't run multiple times.
If you `Global.LuaGlobals.require()` a `Class.ModuleScript` from both sides of the client-server boundary, then the `Class.ModuleScript` returns a unique reference for each side.
### Patterns
Module scripts have some common patterns that you can use to simplify your code and provide more flexibility over the features Roblox Studio provides. By incorporating these patterns into your development, you can avoid common pitfalls as your Roblox experience grows in size and complexity.
#### Data sharing
To associate data with individual objects, you can assign attributes to them or create `Class.Configuration` folders with value objects such as `Class.StringValue` or `Class.IntValue`. However, both approaches are troublesome if you want to add or modify dozens of objects or data values. They also don't store tables or functions.
If you want to modify the same data for multiple copies of the same
object or reuse the same data for different objects, store the data in
`Class.ModuleScript|ModuleScripts`. It's an easier way for you to reuse the data in other scripts, and you can store tables and functions.
The following example `Class.ModuleScript` in `Class.ReplicatedStorage|ReplicatedStorage` stores the configuration values for a generic gun:
```lua title="Weapon Stats"
-- ModuleScript in ReplicatedStorage named GunConfig
local GunConfig = {}
GunConfig.MagazineSize = 20
GunConfig.AmmoCount = 100
GunConfig.Firerate = 600
GunConfig.Damage = {
["Head"] = 50;
["Torso"] = 40;
["Body"] = 25;
}
return GunConfig
```
#### Custom events
Custom events enable scripts to communicate with each other, but having to keep track of references to individual `Class.BindableEvent` objects may clutter your code.
You can use `Class.ModuleScript|ModuleScripts` to store
`Class.BindableEvent|BindableEvents` and provide
custom event handlers that are directly tied to the methods of `Class.ModuleScript`.
The following `Class.ModuleScript` in `Class.ReplicatedStorage|ReplicatedStorage` has a custom event that fires when the switch changes state:
```lua title="Switch Module"
-- ModuleScript in ReplicatedStorage named Switch
local Switch = {}
-- Creating bindable so any script can listen to when the switch was changed
local bindableEvent = Instance.new("BindableEvent")
Switch.Changed = bindableEvent.Event
local state = false
function Switch.flip()
state = not state
bindableEvent:Fire(state)
end
return Switch
```
The following `Class.LocalScript` in `Class.ReplicatedFirst` connects a function to call when the `Switch.Changed` event fires.
```lua
-- LocalScript in ReplicatedFirst
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Switch = require(ReplicatedStorage:WaitForChild("Switch"))
Switch.Changed:Connect(function(newState)
print("Switch state is now", newState)
end)
-- Test the flipping a few times
task.wait(1)
Switch.flip()
task.wait(1)
Switch.flip()
```
#### Encapsulation
Encapsulation is the practice of creating a layer of abstraction around objects or scripting logic to hide complexity. You can use `Class.ModuleScript|ModuleScripts` to encapsulate Roblox objects with custom Luau functions to simplify code.
For example, you can use encapsulation to:
- Simplify cross-network communication with a single `Class.RemoteEvent` object.
- Wrap error handling code around sensitive services such as `Class.DataStoreService`.
- Define custom methods to control or extend Roblox object features.
It's difficult to keep track of dozens of individual `Class.RemoteEvent` objects to implement networking in your experience. You can use a `Class.ModuleScript` to encapsulate a single `Class.RemoteEvent` to help simplify this problem. By including a unique `id` argument, you can still send different network messages while only using a single `Class.RemoteEvent`.
In the example below, the `Class.ModuleScript` named `NetworkManagerClient` encapsulates the `Class.RemoteEvent:FireServer()` method to include this extra `id` argument. Additionally, this `Class.ModuleScript` references the `Class.RemoteEvent` object itself so you don't have to reference it in other parts of your code. You only need to require this `Class.ModuleScript` to send network messages and don't need to deal with `Class.RemoteEvent` objects in the rest of your codebase.
The following `Class.ModuleScript` in `Class.ReplicatedFirst` provides an encapsulated function that you can call on your client scripts to send a network message:
```lua title="Network Module"
-- ModuleScript in ReplicatedFirst named NetworkManagerClient
local NetworkManagerClient = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Encapsulating the remote object's FireServer function
function NetworkManagerClient.FireServer(id, ...)
remoteEvent:FireServer(id, ...)
end
return NetworkManagerClient
```
The following `Class.ModuleScript` in `Class.ServerScriptService` uses `Class.BindableEvent|BindableEvents` for every script to connect to a specific `id`. When a client sends a network message, each `Class.BindableEvent` associated with the specified `id` fires.
```lua
-- ModuleScript in ServerScriptService named NetworkManagerServer
local NetworkManagerServer = {}
local networkSignalList = {}
function NetworkManagerServer.GetServerEventSignal(id)
local bindableEvent = Instance.new("BindableEvent")
-- Linking the new BindableEvent to the id
table.insert(networkSignalList, {
id = id,
bindableEvent = bindableEvent,
})
return bindableEvent.Event
end
-- Connecting to
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
remoteEvent.OnServerEvent:Connect(function(player, id, ...)
-- Finding every bindable event that matches the ID of the received remote event
for _, signal in networkSignalList do
if signal.id == id then
signal.bindableEvent:Fire(player, ...)
end
end
end)
return NetworkManagerServer
```
The following `Class.LocalScript` sends a message with the ID "RequestA" with an optional "Hello" argument.
```lua
-- LocalScript in ReplicatedFirst
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local NetworkManagerClient = require(ReplicatedFirst:WaitForChild("NetworkManagerClient"))
NetworkManagerClient.FireServer("RequestA", "Hello")
```
The following `Class.Script` connects to the network message ID "RequestA" and prints out a statement with any additional parameters when it receives the request.
```lua
-- Script in ServerScriptService
local ServerScriptService = game:GetService("ServerScriptService")
local NetworkManagerServer = require(ServerScriptService:WaitForChild("NetworkManagerServer"))
NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)
print("Received RequestA from", player, ...)
end)
```
## Create scripts
To create script objects in the Studio **Explorer** window:
1. Hover over the parent container into which you want to insert a script.
2. Click the **&CirclePlus;** button that appears to the right of the
container and select the type of script you want to insert.
3. Rename the script.
<Alert severity="info">
See [code samples](../samples/index.md) and [tutorials](../tutorials/index.md) for scripting examples.
</Alert>

View file

@ -0,0 +1,117 @@
---
title: Access control and confidentiality
description: Learn what information and assets are visible to the client and secure them against exploiters.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
Understanding what information and assets are visible to clients is critical for maintaining both the security and confidentiality of your experience. Developers often underestimate how much is visible to exploiters and replicated to clients. Exploiters have the ability to access "restricted" places within your universe if they can join a single place. They can view any content replicated to their client, regardless of whether or not it's visible or currently in use. Additionally, exploiters can decompile any replicated local scripts and ModuleScripts, even if they are never executed on the client.
## Client-side teleportation within universes
Once inside a universe, clients can teleport to any place within that universe, potentially bypassing any intended access restrictions or progression gates. This can also lead to the unintended leak of unreleased content, for example from a development or staging experience. It is important to understand that:
- Disabling "Direct Access" only removes the Join button from subplaces on the website - it does not prevent client-initiated teleports
- Assume exploiters will discover the existence of all subplaces within your universe
- A client can teleport to any subplace if they can access a single place, such as the root place, regardless of your intended design flow
Many developers attempt to prevent access by kicking unauthorized users from a subplace. This may work for blocking gameplay, but it does **not** prevent content from replicating to the client. For places intended to be private:
- **Keep development and test places in separate, private universe**s - this is the only reliable way to ensure confidentiality
Never ship confidential event assets, scripts, or UI elements to the production environment before they're intended to be active
For universes requiring a public place but restricted access, combine multiple layers:
- If feasible, use streaming to limit how much of the world will replicate to a new player.
- Add server-side group role or badge verification and verify player state or progression requirements.
- By default, disallow incoming players. This ensures no player will be allowed even if the verification process is inconclusive, such as in the case of an exception thrown from the engine.
- If practical, use the `Class.Players.BanAsync|Ban API` for players that fail validation. This prevents players from continuously attempting to join on the same account.
## Replication
Replication describes how state transfers over the network between instances of the engine. Roblox's replication model is generally simplified in a few key ways:
- Instances are server-authoritative, meaning that for an instance to replicate between all participants (the server and all connected clients) it must be created on the server.
- Instance properties are also server-authoritative, which means most properties must be changed on the server for the changes to be visible on all clients.
- Generally speaking, an instance either replicates to all connected clients or does not. There are some exceptions, such as streaming.
Replication containers are top-level instances (parented under the DataModel) that replicate to clients. If an instance ever becomes a descendant of a replication container over its lifetime, you should expect much of its state to replicate to all clients. You can read more about common replication containers in the [Data model guide](../../projects/data-model.md#replication). When in doubt, you can always check how an instance or property replicates in a test environment such as Play Solo. You can read more about testing modes in [Studio testing modes](../../studio/testing-modes.md).
### Security implications of replication
Any content that replicates to a client can be extracted and data-mined by an exploiter. As a general rule, avoid publishing or shipping any confidential content to the live production environment unless you are immediately prepared for it to be seen by users. Even if there is logic that gates the release or access of the content in-experience until a later date (or some other condition), assume that exploiters will find a way to discover and leak your content as soon as it is published.
Avoid overly descriptive or predictable names for sensitive instances, including but not limited to: scripts, remotes, and models. Having a predictable DataModel hierarchy makes it easier to develop exploits.
### Script decompilation
Any `Class.LocalScript`, `Class.Script` with `RunContext::Client`, or `Class.ModuleScript` can be decompiled by an exploiter once replicated to their client, even if such scripts are disabled, never required, or never run on the client. Server-only Scripts and ModuleScripts stored in `ServerStorage` or `ServerScriptService` cannot be decompiled as they never replicate to clients.
Writing a ModuleScript that includes server-only and client-only code in the same script is ill-advised since server-sided logic will be exposed in decompilation and can be more easily dissected for bugs that can be exploited.
```lua title="Original ModuleScript in ReplicatedStorage"
local module = {}
local Remote = script:WaitForChild("RemoteEvent")
-- Here's an example of a paradigm to avoid
-- Keep server code in Script instances or in ServerStorage/ServerScriptStorage!
if game:GetService("RunService"):IsServer() then
function module:DoServerThing(password)
if password == "SecretPassword" then
print("sensitive server code")
end
end
Remote.OnServerEvent:Connect(function(player, password)
module:DoServerThing(password)
end)
else
function module:DoServerThing(password)
Remote:FireServer(password)
end
end
return module
```
```lua title="Decompiled result"
local table1 = {};
local RemoteEvent = script:WaitForChild("RemoteEvent");
if game:GetService("RunService"):IsServer() then
function table1.DoServerThing(_, p2) -- Line: 9
if p2 == "SecretPassword" then
print("sensitive server code");
end
end
RemoteEvent.OnServerEvent:Connect(function(player: Player, p3) -- Line: 15
--[[
Upvalues:
[1] = table1
--]]
table1:DoServerThing(p3);
end);
return table1;
end
function table1.DoServerThing(_, p1) -- Line: 19
--[[
Upvalues:
[1] = RemoteEvent
--]]
RemoteEvent:FireServer(p1);
end
return table1;
```
As shown above, decompilation reveals server-side logic including hardcoded passwords, business rules, and implementation details that exploiters can analyze for vulnerabilities.
### Impact of confidentiality breaches
Confidentiality breaches can have significant consequences, such as:
- **Content leaks**: Unreleased items, maps, or features may be discovered and shared publicly before official announcements
- **Competitive disadvantage**: Mechanics, algorithms, or upcoming features may be reverse-engineered by competitors
- **Exploit Development**: Exposed server logic makes it faster and easier for exploiters to identify vulnerabilities and develop targeted attacks

View file

@ -0,0 +1,420 @@
---
title: Securing the client-server boundary
description: Best practices client-server actions to protect your experience from attack vectors.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
Any time a client can trigger an action on the server, there is potential for abuse. While `Class.RemoteEvent|RemoteEvents` and `Class.RemoteFunction|RemoteFunctions` are the most common attack vector here, other instances like `Class.ProximityPrompt` are susceptible. This section covers how to secure this boundary.
## Validating remote inputs
Every piece of data sent from a client must be validated by the server before it's used. Apply these layers of validation based on what the remote does.
### Context/permission validation
This is necessary for remotes that affect experience's state, progression, or other players. The server must verify if the player has the necessary permissions to make the request. For example, is the player close enough to a shop to buy something? Do they have a key required to open a door? Is their character alive?
Validation is necessary for remotes that:
- Grant rewards or modify player progression
- Affect other players or shared experience state
- Perform actions with distance, timing, or permission requirements (for example, shop distance)
### Type and structure validation
Another attack that exploiters might launch is to send technically valid types but make them extremely large, long, or otherwise malformed. For example, if the server has to perform an expensive operation on a string that scales with length, an exploiter could send an incredibly large or malformed string to bog down the server.
Another common attack that exploiters may use involves sending `Library.table|tables` in place of an `Class.Instance`. Complex payloads can mimic what would be an otherwise ordinary object reference.
For example, provided with an [in-experience shop system](#in-experience-shop) where item data like prices are stored in `Class.NumberValue` objects, an exploiter may circumvent all other checks by doing the following:
```lua title="LocalScript in StarterPlayerScripts"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local payload = {
Name = "Ultra Blade",
ClassName = "Folder",
Parent = itemDataFolder,
Price = {
Name = "Price",
ClassName = "NumberValue",
Value = 0, -- Negative values could also be used, resulting in giving currency rather than taking it!
},
}
-- Send malicious payload to the server (this will be rejected)
print(buyItemEvent:InvokeServer(payload)) -- Outputs "false Invalid item provided"
-- Send a real item to the server (this will go through!)
print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Outputs "true" and remaining currency if purchase succeeds
```
```lua title="Script in ServerScriptStorage"
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- Check if the passed item isn't spoofed and is in the ItemData folder
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- The server can then go on to process the purchase based on the example flow below
end
-- Bind "buyItem()" to the remote function's callback
buyItemEvent.OnServerInvoke = buyItem
```
### Value validation
In addition to validating types and data, you should validate the values passed through `Class.RemoteEvent|RemoteEvents` and `Class.RemoteFunction|RemoteFunctions`, ensuring they are valid and logical in the context being requested. This is essential for remotes that handle currency, items, numeric inputs, or user-generated content. You must ensure values are within expected boundaries (e.g., a purchase quantity is greater than zero) and that provided IDs or names are valid (e.g., an itemId corresponds to a real item in your game).
**Special considerations for numeric values:** Both inf and NaN are valid number types, but both can cause major issues if an exploiter sends them and they're not handled correctly. Use functions like these to detect and reject them:
```lua
local function isNaN(n: number): boolean
-- NaN is never equal to itself
return n ~= n
end
local function isInf(n: number): boolean
-- Number could be -inf or +inf
return math.abs(n) == math.huge
end
```
### The danger of NaN
An exploiter can send NaN (Not a Number) as an argument. NaN is uniquely dangerous because it is of type "number" but fails all standard comparisons, allowing it to subtly bypass logical checks that seem safe. Let's look at a vulnerable trading system where a player makes an offer.
```lua
-- VULNERABLE SERVER CODE
local function onCreateTradeOffer(player, offeredGold)
-- 1. TYPE CHECK: This passes! typeof(NaN) is "number".
if typeof(offeredGold) ~= "number" then
return "Invalid offer"
end
-- 2. RANGE CHECK: This is bypassed!
-- (NaN < 0) is false. (NaN > 1000000) is also false. The check does nothing.
if offeredGold < 0 or offeredGold > 1000000 then
return "Offer out of range"
end
-- 3. INVENTORY CHECK: This is bypassed!
-- (NaN > player.Gold.Value) is false.
if offeredGold > player.Gold.Value then
return "Not enough gold"
end
-- VULNERABILITY: A fraudulent trade offer with NaN gold is created!
createTrade(player, {gold = offeredGold})
return "Trade offer created."
end
```
The exploiter successfully created a trade offer without having any gold, because every check failed silently. Furthermore, this single NaN value will now poison any other system that tries to use it, and even spread to other players through the offer.
Below are some high-level examples covering secure design of common in-game features.
### In-experience shop
Consider an in-experience shop system with a user interface, for instance a product selection menu with a "Buy" button. When the button is pressed, you can invoke a `Class.RemoteFunction` between the client and the server to request the purchase. However, it's important that the server, the most reliable manager of the experience, confirms that the user has enough money to buy the item.
<figure>
<img src="../../assets/scripting/security/Remote-Purchase-Flow.png" width="830" alt="Example purchase flow from client to server through a RemoteEvent" />
<figcaption>Example purchase flow from client to server through a `Class.RemoteFunction`</figcaption>
</figure>
### Weapon targeting
Combat scenarios warrant special attention on validating values, particularly through aiming and hit validation.
Imagine an experience where a player can fire a laser beam at another player. Rather than the client telling the server who to damage, it should instead tell the server the origin position of the shot and the part/position it thinks it has hit. The server can then validate the following:
- The position the client reports shooting from is near the player's character on the server. Note that the server and client will differ slightly due to latency, so extra tolerance will need to be applied.
- The position the client reports hitting is reasonably close to the position of the part the client reports hitting, on the server.
- There are no static obstructions between the position the client reports shooting from and the position the client reports shooting to. This check ensures a client isn't attempting to shoot through walls. Note that this should only check static geometry to avoid valid shots being rejected due to latency.
Additionally, you may want to implement further server-side validations as follows:
- Track when the player last fired their weapon and validate to ensure they're not shooting too fast.
- Track each player's ammo amount on the server and confirm that a firing player has enough ammo to execute the weapon attack.
- If you've implemented teams or a "players against bots" combat system, confirm that the hit character is an enemy, not a teammate.
- Confirm that the hit player is alive.
- Store weapon and player state on the server and confirm that a firing player is not blocked by a current action such as reloading or a state like sprinting.
## Rate limiting
Whenever server-side logic can be triggered by the client (via remotes, Touched events, proximity prompts, ClickDetectors, etc.), there is a possibility that this logic could be spammed by exploiters or even legitimate users. Rate limiting controls how frequently these actions can be performed, preventing abuse and system overload. While it is practical (and sometimes necessary) to implement rate limits on the client, never rely solely on a client-sided rate limit.
For any server-sided logic that can be triggered by a client, always consider the maximum rate at which such logic should run. Many server-sided actions must have their frequency limited to prevent abuse or failure, such as:
- **Roblox API calls**: Backend APIs such as `Class.DataStoreService` and `Class.BadgeService` have built-in rate limits that will cause your requests to fail if exceeded
- **Computationally expensive operations**: Actions that consume significant server resources or impact other clients
- Server example: cloning large models from ServerStorage, even if they're never replicated to clients
- **Client example**: instructing all connected clients to update their GUI simultaneously
- **Exploitable mechanics**: Actions that could be abused if performed rapidly, such as granting invulnerability, teleporting players, or awarding currency
### Token bucket example
A robust and common approach for rate limiting is the **token bucket** algorithm. Imagine each user has a bucket that can hold a certain number of tokens. To perform an action, the user must spend a token. The bucket refills with new tokens at a steady rate. This method allows for short bursts of actions when needed, but prevents sustained spam by enforcing an average rate over time.
```lua
--!strict
-- Module located in ServerScriptService
type UserId = number
type Bucket = {
tokens: number,
last: number,
}
type TokenBucketT = {
capacity: number,
refillPerSecond: number,
buckets: { [UserId]: Bucket },
allow: (self: TokenBucketT, userId: UserId) -> boolean,
}
local TokenBucket = {}
TokenBucket.__index = TokenBucket
-- Creates a limiter that allows at most `capacity` events, refilling back to
-- full at a rate of `capacity / windowSeconds` tokens per second.
function TokenBucket.new(capacity: number, windowSeconds: number): TokenBucketT
assert(capacity >= 1, "capacity must be >= 1")
assert(windowSeconds > 0, "windowSeconds must be > 0")
local self: TokenBucketT = {
capacity = capacity,
refillPerSecond = capacity / windowSeconds,
buckets = {},
}
return (setmetatable(self, TokenBucket) :: any) :: TokenBucketT
end
local function refill(b: Bucket, now: number, cap: number, rate: number)
local elapsed = now - b.last
if elapsed > 0 then
b.tokens = math.min(cap, b.tokens + elapsed * rate)
b.last = now
end
end
-- Returns false if the user would exceed their limit
function TokenBucket:allow(userId: UserId): boolean
local now = time()
local b = self.buckets[userId]
if not b then
b = { tokens = self.capacity, last = now }
self.buckets[userId] = b
else
refill(b, now, self.capacity, self.refillPerSecond)
end
if b.tokens >= 1 then
b.tokens -= 1
return true
end
return false
end
return TokenBucket
```
To use the module, you first create a new limiter instance with `TokenBucket.new(capacity, windowSeconds`). The `capacity` is the maximum number of requests a user can make in a quick burst, and the `windowSeconds` determines how long it takes to refill all those tokens. For example, `TokenBucket.new(5, 10)` creates a limiter that allows bursts of up to 5 requests and refills one token every two seconds (10 seconds / 5 tokens).
Before executing protected logic, make a call to the `:allow(userId)` method, denying the action if it returns false. It's also good practice to clear the user's bucket data when they leave to prevent memory leaks.
The following example script demonstrates how to protect a `RemoteEvent` used for a custom chat system from being spammed by players.
```lua
-- Example usage in a server script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local TokenBucket = require(ServerScriptService.TokenBucket)
local ChatRemote = ReplicatedStorage:WaitForChild("ChatRemote") :: RemoteEvent
-- 5 messages per 10 seconds (capacity 5, refills at 0.5 tokens/sec)
local chatLimiter = TokenBucket.new(5, 10)
ChatRemote.OnServerEvent:Connect(function(player: Player, message: string)
if not chatLimiter:allow(player.UserId) then
-- Too fast: drop the request or send a spam warning
return
end
-- Process message (after other validation)
broadcastMessage(player, message)
end)
-- Cleanup to prevent memory leaks
Players.PlayerRemoving:Connect(function(p: Player)
chatLimiter.buckets[p.UserId] = nil
end)
```
## Securing client-triggered instances
`Class.ProximityPrompt|ProximityPrompts`, `Class.ClickDetector|ClickDetectors`, and `Class.DragDetector|DragDetectors` are not remotes, but an exploiter can trigger their events from any distance, at any time, often ignoring properties like `Enabled` or `MaxActivationDistance`. These objects must be secured with the same rigor as remotes.
### ProximityPrompt
- An exploiter can fire any event even if `Enabled` is false on the server.
- `Enabled`, `MaxActivationDistance`, and `RequiresLineOfSight` can be silently changed on the client, so any replicated `Class.ProximityPrompt` can be seen and potentially interacted with from any location.
- The server will accept hold events (such as PromptButtonHoldBegan) even if the server-sided `HoldDuration` is zero. The client can manipulate the `HoldDuration` to perform interactions unnaturally quickly.
- Only the `Triggered` event has a server-side distance check. Other events (like `PromptButtonHoldBegan` and `TriggerEnded`) have no distance check, and can be sent arbitrarily by any client.
### ClickDetector
- There are no checks at all on the server for any events.
- Exploiters can replicate any event at any distance, even if the ClickDetector is disabled (`MaxActivationDistance` of 0 or `DragDetector.Enabled` false).
- Events can be replicated even if the `Class.ClickDetector` is not parented to something clickable.
### DragDetector
- For events inherited from ClickDetector, there are no checks.
- For dragging events, the `Enabled` property and `PermissionPolicy` properties are checked and respected by the server. All other properties are not checked.
When one of these events fires, your server script should perform its own validation before taking any action:
- Check if it's enabled: Ensure that the instance is intended to be enabled by checking its properties (such as Enabled, HoldDuration, RunLocally, etc.) on the server. This step isn't necessary if the object is always enabled.
- Check player state: Ensure that the player can perform the action, given the player's current state. Is the player's character in the world? Is their character close enough to the object? Should the player need to be alive to interact with this object? Check any other player state maintained by your experience on the server as needed.
- Apply rate limiting: As covered in [Rate Limiting](#rate-limiting), apply cooldowns on the client and enforce rate limits on the server to prevent these events from being spammed and abused. For interactions that are expected to take a minimum amount of time, ensure that clients are not completing them too quickly.
**Note on network ownership**: By default, if any of these objects are children of unanchored parts or an unanchored assembly, an exploiter can take network ownership of the parent parts and move them directly to their character, bypassing distance checks. For critical actions, use anchored parts or the network ownership API along with the necessary server-sided validation.
### RemoteEvents and RemoteFunctions
Limit the scope and impact of `Class.RemoteFunction|RemoteFunctions` and `Class.RemoteEvent|RemoteEvents`. Take extreme care in dynamically loading any assets (even textures or sounds) or running experience code (especially via require) based on the arguments of remote functions/events. Avoid implementing remotes that allow a client to specify an arbitrary path or instance reference for the server to delete or modify, even if such modifications seem trivial. Remotes that can change arbitrary instance state or make widespread changes to the DataModel tree can often be chained with other bugs to severely impact overall state or logic. Check not only the type of instance arguments, but also the class and expected location or structure within the DataModel.
## Client to client communication
Some remotes are designed to let one client trigger effects on other clients. This happens when a client fires a remote to the server, which then uses `RemoteEvent:FireAllClients()`, `RemoteEvent:FireClient()`, or similar methods to relay information. This pattern is dangerous if not properly secured - the server must be a gatekeeper, not just a relay.
Imagine a scenario where players can cast a lightning spell. When cast, nearby players see the flash, hear the sound, and experience camera shake.
A vulnerable server script might look like this:
```lua title="Vulnerable server code (script in ServerScriptStorage)"
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- This server script relays the message to everyone with no validation
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
castLightningEvent:FireAllClients(strikePosition)
end)
```
```lua title="Client effect code"
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- Create visual and sound effects on the client
local function createLightningEffect(strikePosition)
-- Code to shake the camera
-- Code to play a loud lightning sound
-- Code to create a visual lightning bolt
print("Lightning strikes at: " .. tostring(strikePosition))
end
-- When the server broadcasts the event, this client runs the effect
castLightningEvent.OnClientEvent:Connect(createLightningEffect)
```
An exploiter can abuse this vulnerable system by:
- **Spamming**: Calling the remote hundreds of times per second, causing continuous effects that make the experience unplayable
- **Invalid data**: Sending nil, NaN, or wrong types that cause script errors on all clients
- **Unauthorized use**: Casting spells they haven't unlocked or don't have resources for
The server must be a **gatekeeper**, not a relay. Before broadcasting, it must validate the request and apply rate limiting per-player. Here is an example of some methods that could be employed to validate the request.
```lua title="Secure server code"
local castLightningEvent = game.ReplicatedStorage.CastLightning
local playerCooldowns = {}
local COOLDOWN_TIME = 3
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
-- 1. Type validation
if typeof(strikePosition) ~= "Vector3" then
return -- Invalid type, reject silently
end
-- 2. Check for NaN (NaN ~= NaN is the only way to detect it)
if strikePosition.X ~= strikePosition.X then
return -- Contains NaN, reject
end
-- 3. Example Rate limiting
local lastCast = playerCooldowns[player] or 0
if tick() - lastCast < COOLDOWN_TIME then
return -- Still on cooldown
end
-- 4. Example Permission check
if not player:GetAttribute("HasLightningSpell") then
return -- Player doesn't have this spell
end
-- 5. Example Range validation
local character = player.Character
local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return
end
local distance = (humanoidRootPart.Position - strikePosition).Magnitude
if distance > 100 then
return -- Out of range
end
-- All checks passed - safe to broadcast
playerCooldowns[player] = tick()
castLightningEvent:FireAllClients(player, strikePosition)
end)
```
By implementing these checks, it becomes harder for an exploiter to ruin the experience for other players. The server acts as a protective barrier, ensuring only valid, authorized actions are broadcast to other clients.
## Special considerations
The following are some additional cases you should consider that may require specialized handling.
### Data store manipulation
In experiences using DataStoreService to save player data, exploiters may take advantage of invalid data, race conditions to corrupt saves or duplicate items in a DataStore. This is especially problematic in experiences with item trading, marketplaces, and currency systems.
Ensure that any actions performed through a `Class.RemoteEvent` or `Class.RemoteFunction` that affect player data with client input is sanitized based on the following:
- **Instance values** cannot be serialized into a DataStore and will fail. Utilize type validation to prevent this.
- **DataStores have data limits**. Strings of arbitrary length should be checked and/or capped to avoid this, alongside ensuring limitless arbitrary keys cannot be added to tables by the client.
- **Table indices cannot be NaN or nil**. Iterate over all tables passed by the client and verify all indices are valid.
- **DataStores can only accept valid UTF-8 characters**, so you should sanitize all strings provided by the client via utf8.len() to ensure they are valid. utf8.len() will return the length of a string, treating unicode characters as a single character; if an invalid UTF-8 character is encountered it will return nil and the position of the invalid character. Note that invalid UTF-8 strings can also be present in tables as keys and values.
- **Infinite/NaN number exploits** - Exploiters may send values like -1/0, 0/0, or math.huge to cause infinite currency or break calculations. Always validate using the methods described in [the danger of NaN](#the-danger-of-nan).
Race conditions can occur when players manipulate timing between operations to duplicate items or corrupt data.
**Trading exploit example:** A player initiates a trade, sends their item to another player, then immediately leaves the game. If the trade completes but their DataStore save fails due to invalid data, they rejoin with their original items while the other player keeps the traded items - resulting in duplication.
**Prevention strategies:**
- Validate all data before any trade operations
- Use transaction-like patterns where all players' data is validated before committing any changes
- Implement proper error handling that reverts all changes if any part fails
### MarketplaceService
For interactions involving `Class.MarketplaceService`, such as passes or developer products, all purchase validation and item granting must occur on the server. Specifically, utilize the `ProcessReceipt` callback to securely validate purchase receipts. Never trust a client-side signal, such as `PromptProductPurchaseFinished`, to confirm a purchase without server verification, as these can be spoofed. Ensure that your `ProcessReceipt` function thoroughly checks the receipt details and only grants items or currency after confirming a legitimate transaction with Roblox servers, and be prepared to handle cases where a product has already been granted for a given receipt.

View file

@ -0,0 +1,61 @@
---
title: Defensive design tactics
description: Understand fundamentals of design that help prevent cheaters and exploiters from ruining the playing experience.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
Basic design decisions can serve as "first step" security measures to discourage exploits. The core principle is to design systems where cheating is either impossible or provides no meaningful advantage, rather than trying to detect and prevent cheating after the fact.
Consider a shooter game where players earn points for kills. An exploiter might create bots that teleport to the same location to be repeatedly killed for easy points. This scenario illustrates the difference between reactive and defensive approaches:
<table><thead>
<tr>
<th>Approach</th>
<th>Predictable outcome</th>
</tr></thead>
<tbody>
<tr>
<td>Chase down bots by writing code that attempts to detect them (reactive approach).</td>
<td>
<Alert severity = 'error'>Exploiters seeking quick points will find a way around your complex detection code and use their bots in a different way.</Alert>
</td>
</tr>
<tr>
<td>Reduce or outright remove point gains for kills on newly spawned players (defensive approach).</td>
<td>
<Alert severity = 'success'>Extra time and friction is now required for exploiters because they get no points for instantly killing their bots. Additionally, "spawn campers" are discouraged because they no longer get points for killing newly spawned players.</Alert>
</td>
</tr>
</tbody>
</table>
Additional defensive design scenarios:
<table><thead>
<tr>
<th>Potential exploit scenario</th>
<th>Reactive Approach (less effective)</th>
<th>Defensive approach (more effective)</th>
</tr></thead>
<tbody>
<tr>
<td>(Obby) Exploiter teleports to the end to claim rewards</td>
<td>Only check the player's final position and try to detect impossible completion times</td>
<td>Design with mandatory, sequential checkpoints. Server validates each checkpoint was activated in order before granting rewardsYou can add another layer by flagging players who complete stages faster than humanly possible. The design of the experience (requiring checkpoints) is what enables the effective server-side validation.</td>
</tr>
<tr>
<td>(Combat) Client reports dealing impossible damage amounts</td>
<td>Try to detect and filter out impossible damage values</td>
<td>Server calculates all damage from server-sided weapon stats and validates hits through server-side raycasting</td>
</tr>
<tr>
<td>(Economy) Exploiter duplicates items through rapid requests</td>
<td>Try to detect duplicate requests or unusual patterns</td>
<td>Implement server-side cooldowns and validate all transactions against current player inventory state</td>
</tr>
</tbody></table>
<Alert severity = 'info'> <AlertTitle>Key principle</AlertTitle> <br /> When designing a new feature, ask yourself: "How would an exploiter abuse this, and can I change the design to make that abuse impossible or worthless?" Instead of trying to detect cheating after it happens, design your experience so that cheating either cannot occur or provides no meaningful advantage.</Alert>

View file

@ -0,0 +1,47 @@
---
title: Network ownership, movement validation, and physics
description: Learn about network ownership and its relationship to assemblies, physics, and more.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
[Network ownership](../../physics/network-ownership.md) is one of the more nuanced and commonly misunderstood aspects of the Roblox engine. When a player has network ownership of a part or assembly, they have complete authority over the physics simulation for those objects. By default, the server grants network ownership of unanchored parts to clients with nearby player characters to distribute the load of the physics simulation. This system improves responsiveness but creates significant security implications that every developer must understand.
## Understanding network ownership
When a client has network ownership over parts (including their character), they have access to:
**Assembly manipulation:**
- Teleport to any position.
- This can be used to quickly gain ownership and control of as many unanchored parts and assemblies as the server will allow.
- Manipulate their movement and state, such as flying or changing their speed.
- Exploiters can set their Humanoid WalkSpeed to any value. Properties can be modified locally without firing any events on the local client, and without such property changes capable of being read from local scripts.
- Play arbitrary animations and manipulate animation state.
- Bypass physical obstacles and alter their collision.
**Physics manipulation**
- Control the position and rotation of any unanchored parts or mechanisms, including replicating `Inf` or `NaN` components in `Datatype.CFrame|CFrames`.
Set part velocities to extreme values (including `Inf` or `NaN`), which can interfere with the physics of other unanchored parts/assemblies, even those that are not owned by the exploiter.
This is often used to fling other player characters and nearby parts.
Manipulate the firing of Touched events, including not firing Touched at all.
Should your experience necessitate gameplay-critical fully unanchored parts or assemblies, consider using the network ownership API to manually set ownership or turn off automatic ownership.
## Movement validation
In competitive experiences, server-side validation of player movement is crucial for simulation integrity, as clients control character movement and physics. However, a universal solution doesn't exist due to varying gameplay mechanics.
Traditional methods often fall short in complex scenarios. Simple distance-over-time calculations may suffice for competitive shooters but fail in physics-intensive experiences involving vehicles, flight, or intricate interactions. Validation must also account for network latency.
<Alert severity = 'warning'>
[Server authority](../../projects/server-authority/) is currently in beta and will be released soon.
The most reliable solution for preventing physics and movement exploits is server authority, which moves physics simulation and movement validation entirely to the server. This eliminates the fundamental security weakness of client-controlled movement by ensuring all movement decisions are made on the server with authoritative validation.
</Alert>
Until server authority becomes available, developers implementing client-side validation must understand that effective approaches vary dramatically. Basic heuristics can flag innocent players with unstable connections, and straightforward speed calculations break down in experiences with complex movement systems. Position updates are subject to internet latency, requiring averaging over time. Many developers find that leaky bucket-style accumulators work well for handling burst movements while preventing sustained violations.
Practical implementation often requires projecting movement onto specific planes (e.g., XZ for ground-based movement) to avoid penalizing legitimate vertical movement while detecting horizontal teleportation. Experiences with legitimate teleportation mechanics need explicit exemption systems. Validation frequency, tolerance levels, and enforcement strategies should be tuned to each experience's specific requirements.

View file

@ -0,0 +1,50 @@
---
title: Security and cheat mitigation tactics
description: Explore security tactics and cheat mitigation tactics for Roblox experiences.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
Before diving into specific tactics to developing securely and prevent cheating, it's essential to understand the foundational principles of Roblox security. A secure experience is built on a mindset that anticipates adversarial actions. Before writing a single line of code, you must internalize these foundational principles. They should inform every architectural and design decision you make.
## Never trust the client
This is the foundational principle. A determined exploiter has complete control over their local state and network traffic. Because exploiters have this level of control, any security measure that relies on client-side enforcement will eventually be bypassed. This is not a limitation of Roblox: it's a fundamental reality of client-server architectures. Assume every piece of data sent from the client has been manipulated, fabricated, or sent with malicious intent. This includes the power to:
- Decompile any replicated LocalScript or any ModuleScript, even if they never run on the client
- Take network ownership of their character and any unanchored parts
- Trigger client-initiated events such as Touched events or ProximityPrompt activations at any range or frequency
- Modify their player's position, physics, or interactions with the world
- Fire or invoke RemoteEvents and RemoteFunctions at any frequency with arbitrary arguments (besides the first Player argument)
- Change anything in their local DataModel without firing any expected events
- Arbitrarily alter the behavior of any locally running code
Because of this, all critical logic must be validated server-side or run exclusively on the server. The consequences of this control are detailed in [Network Ownership, Movement Validation, and Physics Exploits](./network-ownership.md) and [Access Control and Confidentiality](./access-control.md).
## Server authority
The server must be the ultimate source of truth for all simulation states, rules, player progression, and critical decisions. The client's role is to render the world and send user input to the server.
The server's role is to:
- Receive input from the client
- Validate that the requested action is possible and permissible
- Execute the action and update its authoritative state
- Replicate the results to all relevant clients
For example, if a player says "I want to buy a Bloxy Cola" the server must know the item's true price, the player's money, and the player character's physical distance from the shop before validating and approving the transaction. As much as possible, the state to be validated should be maintained exclusively by the server and not by clients. In the example, if the Bloxy Cola transaction is approved, the server should subtract the Bloxy Cola's price from the player's money, however, the server cannot necessarily always control the player's character.
## Security by design
Integrate security considerations into your experience's design from the very beginning, rather than attempting to bolt them on later as an afterthought.
- Threat model every new feature. For every new feature, ask
- How could an attacker exploit this if they have full control of their client?
- If a client could send any value for any parameters used in the feature, what's the worst possible outcome?
- What happens if this feature is used 1,000+ times per second? What is the maximum rate at which it should be used?
- Could an exploiter use this to ruin another player's experience?
- How much resources, in the worst case, could this feature feasibly use?
- What is the minimum internal information or state that I need to expose for this feature?
- Partition responsibilities early. Keep logic and data in ServerScriptStorage from day one. Never place them in replicated containers, e.g. ReplicatedStorage or Workspace.

View file

@ -0,0 +1,81 @@
---
title: Server-side detection and consequencing
description: Find ways to respond to abusive looking behavior and respond accordingly.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
This section assumes you already validate inputs and context (see previous sections). This article covers what to do after a request is technically valid but behavior looks abusive, plus how to respond without harming legitimate players.
## Philosophy
- **The server decides**. Detection and consequencing live on the server. Try to never ban based on client-side detections, as exploiters can easily bypass them.
- **Prevent harm first**. Quietly neutralize the impact of an exploit. For example, if a player is speed-hacking, just rubber-band them back to their last valid position instead of kicking them immediately. Punish only when necessary to protect the experience or other players.
- **Be proportional and reversible**. Assume false positives can happen. Prefer actions you can roll back, such as temporary suspensions or resource removal, over permanent bans.
- **Design > detection**. Your primary security comes from robust validation and rate limiting (covered in [Securing the client-server boundary](./client-server-boundary.md) and other sections). The detection methods below are meant to supplement those defenses, not replace them.
## Heuristics
Heuristics are behavior checks that flag actions that are technically possible but highly improbable for a legitimate player. They are "rules of thumb" for catching suspicious activity that slips past basic validation. An effective heuristic compares a player's action against a theoretical maximum or a reasonable baseline.
For example, if a player suddenly claims to have gained 1 billion coins in your simulator game, your remote validation might confirm the request is structured correctly. But a heuristic check on the server would know that earning that much should take weeks, not seconds, and flag the transaction as suspicious.
Here are three classic examples of server-side heuristics. Each one validates player behavior against the experience's rules and physical limitations, creating a strong defense against common exploits.
<table><thead>
<tr>
<th>**Server-side heuristic**</th>
<th>**Description**</th>
<th>**Example**</th>
</tr></thead>
<tbody>
<tr>
<td>**Fastest Completion Time**</td>
<td>Calculate the theoretical fastest possible time to complete a task, like an obstacle course. This involves finding the optimal path and assuming maximum player speed.</td>
<td>In an obby, if the world record is 30 seconds, a completion time of 5 seconds is a strong signal of teleportation or speed exploits.</td>
</tr>
<tr>
<td>**Rate of Gain**</td>
<td>Track the maximum legitimate rate at which a player can earn a resource (currency, experience, items).</td>
<td>If a player can legitimately earn 100 coins per minute, a sudden gain of 10,000 coins in one second should be flagged.</td>
</tr>
<tr>
<td>**Action Cadence**</td>
<td>The server analyzes the time intervals between a player's repeated actions. Human inputs have natural, slight variations in timing, whereas simple automation scripts (like auto-clickers) often perform actions with robotic consistency.</td>
<td>In a fishing minigame, a player clicks to cast their line. The server logs the timestamp of each cast. If the player casts their line 100 times in a row with the exact same interval (e.g., precisely 2.500 seconds) between each action, it's a strong indicator of a macro or bot. A human's timing would naturally fluctuate</td>
</tr>
</tbody></table>
Any one of these examples may not be enough on its own to convict a cheater. Each should be treated as a signal, not as definitive proof. A good practice is to implement a suspicion score. When a player fails a heuristic check, you increment their score. Severe actions like kicking or banning should only occur after multiple, varied detections have pushed a player's score past a high threshold.
For example, suppose a skilled player finishes your obby with a record-shattering time, triggering your "Fastest Completion Time" heuristic. Should they be banned? It's possible they're exploiting, but they might also have discovered a legitimate but unintended shortcut. Banning them based on this single signal would be risky and could punish one of your most dedicated players.
However, if that same player's run also flagged the "Action Cadence" heuristic for inhumanly consistent inputs, and an hour later they trigger the "Rate of Gain" heuristic in your lobby, the case becomes much stronger. The combination of these different signals paints a much more reliable picture of cheating. The strength of this system lies in correlating multiple data points to build a confident case before taking action.
## Honeypots
One way to detect cheaters trying to probe your experience's RemoteEvents is to use a decoy called a honeypot. A honeypot is a `Class.RemoteEvent` or `Class.RemoteFunction` that appears legitimate to an exploiter but is never used by any of your legitimate client scripts. Because no normal player should ever be able to fire this remote, any traffic it receives must be from an exploiter. When the server detects this remote being fired, it's a very high-confidence signal that the client is meddling. The best immediate action is to log the incident and kick the player from the session.
Another variation is to design RemoteEvents with specific directional purposes - some exclusively for client → server communication and others exclusively for server → client communication. While the event names don't reveal their intended direction, the server tracks which direction each remote should be used. If an exploiter attempts to fire a server → client remote from the client, this serves as a reliable detection method since legitimate gameplay would never trigger communication in the wrong direction.
## Applying consequences
When detection systems flag suspicious behavior, consider your response strategy carefully. Different experiences require different approaches based on their community, competitive stakes, and tolerance for disruption.
**The consequence ladder** - Most experiences benefit from escalating responses rather than jumping straight to permanent bans:
- **Silent logging** - Build evidence without player impact
- **Quiet mitigation** - Block the exploit's effect (drop requests, clamp values, sync correct state)
- **Temporary restrictions** - Limit specific capabilities (trading, leaderboards, matchmaking)
- **Visible enforcement** - Kicks, temporary suspensions, or permanent bans
**Considerations for your strategy**
- **Delay visible consequences** to avoid teaching exploiters exactly what triggered detection
- **Match severity to response** - minor economic exploits may warrant different treatment than game-breaking physics cheats
- **Consider your community** - competitive experiences may need stricter enforcement than casual experiences
- **Plan for mistakes** - prefer reversible actions when detection confidence is lower
For persistent offenders, consider using the `Class.Players.BanAsync|Ban API` rather than relying solely on kicks. The Ban API prevents banned players from rejoining and provides better tracking across your universe. Version your detection rules and track consequence rates so you can identify problematic systems. Monitor player feedback to gauge whether your enforcement feels fair to the community. The right balance depends entirely on your experience's needs and your players' expectations.

View file

@ -0,0 +1,32 @@
---
title: Vulnerabilities from third-party assets
description: Learn about vulnerability vectors from third-party assets.
---
<Alert severity = 'warning'>
The following content covers various concepts and tactics to improve security and mitigate cheating in your Roblox experiences. It's highly recommended that all developers read through these to ensure that your experiences are secure and fair for all users. Check the sidebar for additional security topics.
</Alert>
Third-party assets from the Creator Store (Toolbox) are a common source of security risks, as they can contain malicious scripts called backdoors. These scripts give their creators unauthorized server-side access to your experience, allowing them to manipulate its state, steal sensitive data, or disrupt the experience for your players.
Backdoors are particularly dangerous because they often appear in otherwise legitimate-looking models. A seemingly innocent building or vehicle might contain a script that activates when certain conditions are met, such as when a specific player joins or a particular command is executed. When a model contains scripts, this is indicated in the Toolbox user interface before and during insertion.
Roblox moderates Models for malicious scripts both proactively and in response to reports, but this process is not guaranteed to remove all such scripts.
## Protecting your game
The most effective defense against backdoors is sandboxing. When you insert a model, you should contain its scripts to prevent malicious code from reaching sensitive APIs or affecting systems outside the model itself.
1. Set `SandboxedInstance` to `Experimental` on Workspace.
2. In the Properties window, set the `Sandboxed` property of the model to `true`.
3. Configure the `Capabilities` property to grant only the specific permissions the asset needs to function.
<Alert severity = 'warning'>
Note that during the beta, some features may not function as expected when `Sandboxed`, erroring with a message saying "...lacking Capability Unassigned". These will be fixed as Capabilities rolls out of beta.
</Alert>
Be especially cautious about granting Network, DataStore, AssetRequire, CapabilityControl, or LoadString capabilities to third-party assets unless you understand exactly why the asset needs them. These capabilities provide access to powerful systems that backdoors commonly exploit.
Before using any asset, carefully inspect its scripts. Pay particular attention to obfuscated or unusually complex code. Malicious actors often use deceptive techniques to hide harmful content, such as using whitespace to position malicious code off-screen when viewed in the studio editor. Consider favoring highly-rated community assets that have been vetted by other developers, though remember that popularity doesn't guarantee safety. For comprehensive guidance on configuring script restrictions, see [Script Capabilities](../../scripting/capabilities.md).
<Alert severity ='success'>In the future, we plan to automatically sandbox all assets from the Creator Store with appropriate Capabilities.</Alert>

View file

@ -0,0 +1,97 @@
---
title: Services
description: Services provide pre-built functionality in the Roblox Engine.
---
In [Reuse code](module.md), you might have noticed frequent use of the `game:GetService()` method. Roblox services let you access the built-in features of the engine, like selling in-experience items, enabling chat, playing sounds, animating objects, and managing instances.
In fact, services are the first step in **the most fundamental, common pattern of Roblox development**:
1. Get services.
1. Require module scripts.
1. Add local functions.
1. Add the [events](events/index.md) that trigger those functions.
For example, you might want to save players' positions in the world when they exit your experience:
```lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SaveManager = require(ReplicatedStorage:WaitForChild("SaveManager"))
-- Local function that calls a reusable function in the module script.
local function saveProgress(character)
-- Get the position of the player's character.
local position = character:FindFirstChild("HumanoidRootPart").Position
-- Use the saveData function in the module script, which writes to the
-- DataStoreService.
SaveManager.saveData(character, position)
end
-- Another local function that calls saveProgress() when a character is removed
-- from the experience (in this case, when the player leaves).
local function onPlayerAdded(player)
player.CharacterRemoving:Connect(saveProgress)
end
-- Calls onPlayerAdded when a player first connects to the experience.
Players.PlayerAdded:Connect(onPlayerAdded)
```
Some key details include:
- Because you should only retrieve a service once per script, the convention is to give the variable the same name as the service. This convention applies to module scripts, as well.
- You retrieve services with the global variable `Class.DataModel|game`, a reference to the root of the data model.
- Roblox doesn't make guarantees around loading order (and [instance streaming](../workspace/streaming.md) further complicates what is and isn't loaded at any given time), so the use of `Class.Instance:WaitForChild()` is an important safety measure.
Rather than comparing [standard libraries](/reference/engine/libraries), [global functions and variables](/reference/engine/globals), or third-party libraries, a big part of Roblox development is identifying which of the many, many services can help you add the desired functionality to your experiences. In the example above, instead of using a standard I/O library to write to disk, you use [cloud services](#cloud-services) to store data.
## Container services
Container services can contain and influence other objects. These container
services reside at the root of the data model and are visible in Studio's
Explorer window. Collectively, these container services
form a structured hierarchy for the data model, so the Roblox Engine can
properly interpret and render your place. The following table includes some common container services.
Service | Description
:--- | :---
`Class.Workspace` | Contains all objects that render in the 3D world, such as parts and terrain.
`Class.Lighting` | Contains objects for setting universal lighting effects, such as `Class.Atmosphere` and `Class.Sky`.
`Class.ReplicatedStorage` and `Class.ReplicatedFirst` | Contain content and logic that replicates between the server and client.
To further examine the data model, you can use these methods:
- `Class.DataModel:FindService()|game:FindService()` searches for the instance of the specified service.
- `Class.DataModel:GetChildren()|game:GetChildren()` returns an array of all root children of the data model, which are the top-level container services.
- `Class.DataModel:GetDescendants()|game:GetDescendants()` returns an array of all the descendants of the data model, including all container services and their children.
For more information on container services, see the [data
model](/projects/data-model#object-organization) documentation.
## Scripting services
Scripting services provide standard functionality in the Roblox Engine that you
call within scripts. The following table includes some common scripting services.
Service | Description
:--- | :---
`Class.TweenService` | Used to interpolate numeric properties of other instances from a start to end value, with options for easing direction and style, repeat, and delay.
`Class.MarketplaceService` | The service responsible for in-experience transactions, such as prompting the player to purchase a developer product, subscription, pass, Roblox Premium, etc.
`Class.RunService` | Contains methods and events for frame-by-frame time management, as well as for checking the context (server, client, Studio mode) in which the experience is running. Useful for running any process or update on every runtime frame.
`Class.SoundService` | Controls various global aspects of how audio plays in an experience, such as the doppler scale and volumetric audio. Can also contain sound groups to control the volume and dynamic effects properties of multiple audio signals at once.
`Class.CollectionService` | Manages groups (collections) of instances with tags that replicate from the server to the client, letting you more easily assign and work with groups of related instances.
## Cloud services
Roblox also has special cloud services for handling tasks and processes
that occur in the Roblox cloud. The following table includes some common cloud services.
Service | Description
:--- | :---
`Class.DataStoreService` | For storing persistent data across sessions.
`Class.MemoryStoreService` | For storing frequent and ephemeral data that change rapidly.
`Class.MessagingService` | For communicating between multiple servers during live sessions.
Cloud services also have corresponding web APIs; they're accessible from external scripts or tools. For more information, see [Open Cloud](../cloud/guides/index.md).

View file

@ -0,0 +1,107 @@
---
title: Script Sync
description: Use Studio's Script Sync feature to edit your Luau scripts in an external text editor.
---
import BetaAlert from '../includes/beta-features/beta-alert.md'
<BetaAlert betaName="Script Sync" leadIn="Script Sync is currently in Studio beta. To enable this feature, select " leadOut="." components={props.components} />
Roblox Studio has a built-in [script editor](../studio/script-editor.md) for editing the code in your Roblox project. The editor includes key features like linting, type checking, autocomplete and collaborative editing.
Many developers, however, have a preferred text editor, such as [Visual Studio Code](https://code.visualstudio.com), [Cursor](https://cursor.com), [Sublime Text](https://www.sublimetext.com), or [Notepad++](https://notepad-plus-plus.org). Popular development environments often have features or plugins that don't exist in Studio. To use an external text editor with Studio, enable the **Script Sync** feature.
Script Sync works by synchronizing the scripts in your [data model](../projects/data-model.md) to text files on your local disk. Changes made to the Luau files on disk are applied to the scripts in Studio, and vice versa. This synchronization means that you can edit a script in an external editor like VS Code and immediately see the change in Studio.
## When to use Script Sync
- If you want to use your preferred text editor or check code into version control, but use Studio for everything else, Script Sync is a good fit. It also works with Roblox's [built-in collaboration tools](../projects/collaboration.md), which third-party tools often do not.
Script Sync works well with existing Roblox projects, as it's an extra feature rather than a total shift in how you manage the project.
- If you want to check your entire project (not just code) into version control or use your file system as the source of truth for your project, [third-party tools](../projects/external-tools.md) like Rojo are a better choice.
<Alert severity="info">
It is currently not possible to control Roblox Studio's debugger from an external development environment.
</Alert>
## Set up Script Sync
1. In the **Explorer**, select one or more folders that you want to sync.
1. Right-click and select **Script Sync** ⟩ **Sync with Directory**.
<img src="../assets/studio/explorer/Script-Sync.png" alt="The Explorer window with the Script Sync menu expanded." width="400" />
1. Choose a directory on your computer (or create a new one) and click **Save**.
1. In Studio, right-click on the folder again and select **Script Sync** ⟩ **Reveal in Explorer** (Windows) or **Reveal in Finder** (macOS).
1. Open the directory in your preferred text editor.
Alternatively, right-click an individual script in Studio and select **Open in External Editor**.
You can sync individual scripts, but we **highly** recommend syncing folders; if you sync a folder, Script Sync automatically syncs its child folders and scripts, which lets you create, delete, and rename scripts without having to enable syncing each time.
After you set up Script Sync, you almost certainly want to [add an LSP extension](#set-up-a-language-server) to your text editor.
<Alert severity="success">
You only have to set up syncing once. When you restart Studio and open your place, Script Sync remembers what you were syncing and resumes automatically.
</Alert>
## Key tips
- Script Sync only syncs `Class.Script`, `Class.LocalScript`, `Class.ModuleScript` and `Class.Folder` instances. All other instances in a synced folder are ignored. Avoid syncing folders that contain scripts alongside other instances.
- Don't sync scripts that have attributes or tags. Script Sync ignores these, which can lead to data loss if you create or delete scripts or start syncing with an older version of your files on disk.
- Script Sync requires the names of scripts be compatible with your file system—no duplicate names or unsupported characters. If Studio encounters any of these issues, it throws an error and asks you to resolve the situation before resuming sync.
- If any synced files or folders change while Studio is closed, Studio asks you how you want to resolve the situation when you reopen the place.
- Deleting a top-level (or "root") instance—the instance you initially synced—on your local disk can cause Studio to throw an error. To delete a top-level instance:
1. In Studio, right-click the script or folder and select **Script Sync** ⟩ **Stop Sync**.
1. Choose the **Delete file(s)** or **Delete folder(s)** option to delete the instance from your local disk.
1. Delete the script or folder in Studio.
You can delete child instances in Studio or on your local disk without disabling syncing, and the deletion syncs as you'd expect.
## Set up a language server
Language servers give your text editor language-specific features like autocomplete, type checking, "go to definition," and more. For VS Code, we recommend [Luau Language Server](https://marketplace.visualstudio.com/items?itemName=JohnnyMorganz.luau-lsp).
Language server protocol (LSP) extensions need to understand the data model hierarchy of a project to work properly. The VS Code extension comes with a companion Studio plugin ([Luau Language Server Companion](https://create.roblox.com/store/asset/10913122509/Luau-Language-Server-Companion)) that provides this data.
**We highly recommend installing both.** If you use the VS Code extension and companion Studio plugin, language features will work correctly in VS Code while using Script Sync.
## Recommended extensions
<Alert severity="info">
The community maintains these extensions. We recommend them based on their functionality, but can't offer guarantees about performance or security. Before installing an extension, check that it comes from a reputable source and isn't malicious.
</Alert>
- [Roblox Luau LSP](https://marketplace.visualstudio.com/items?itemName=JohnnyMorganz.luau-lsp) and [Luau Language Server Companion](https://create.roblox.com/store/asset/10913122509/Luau-Language-Server-Companion) - Language features (see above)
- [selene](https://marketplace.visualstudio.com/items?itemName=Kampfkarren.selene-vscode) - Static code analysis (linting)
- [StyLua](https://marketplace.visualstudio.com/items?itemName=JohnnyMorganz.stylua) - Automatic code formatting
## Sync rules
When syncing an instance with children with a directory on disk, Studio will automatically sync create, rename, move, and delete operations on children with the corresponding files and folders on disk. This approach lets you maintain your code without having to manually stop and start syncing on individual scripts.
On disk, Script Sync uses the following rules to disambiguate the different types of scripts from each other.
| Naming convention on disk | Instance type in Studio |
| :---- | :---- |
| `name.luau` | `Class.ModuleScript` |
| `name.server.luau` | Script with `Server` `Class.Script.RunContext` |
| `name.client.luau` | Script with `Client` `Class.Script.RunContext` |
| `name.local.luau` | `Class.LocalScript` |
| `name.legacy.luau` | Script with `Legacy` `Class.Script.RunContext` |
| `name.plugin.luau` | Script with `Plugin` `Class.Script.RunContext` |
| `name/` (directory) | `Class.Folder` |
| `name/init.*.luau` | Script instance with children. Type (`*`) is determined by the above rules. |
## Settings
Script Sync has several settings in the **Studio Settings** window (<kbd>Alt</kbd><kbd>S</kbd> or <kbd></kbd>⌥<kbd>S</kbd>). The default options are a good fit for most use cases.
| Setting | Description |
| :---- | :---- |
| Auto resume sync on place open | Whether Studio should resume syncing when you open a place. Disabling this setting dramatically reduces the utility of Script Sync. |
| Resume conflicted sync on place open | How Studio should handle conflicts when you open a place. The default option, **Do not resume**, forces you to resolve conflicts manually to avoid data loss. You can also choose to prefer the version on disk or the version in Studio. |
| Keep local files/directories after sync | How to handle files on disk after you select **Script Sync** > **Stop Sync** in Studio.
| File extension | The file extension to use for scripts on disk.