Skip to content

Scripting and Luau

Nova runs a Luau VM. That means modern Luau syntax is accepted at runtime, not just by the editor:

local total = 0
for _, value in { 1, 2, 3 } do
    total += value
end

local function classify(n: number): string
    if n > 10 then
        return "large"
    elseif n > 3 then
        return "medium"
    end
    return "small"
end

for i = 1, 5 do
    if i == 3 then
        continue
    end
    print(classify(i))
end

Type annotations are erased at runtime. They are useful for tooling and for catching mistakes in Studio, but they do not change behavior.

Standard libraries

Library Status Notes
task Supported spawn, defer, delay, wait, cancel, and the rest.
bit32 Supported
utf8 Supported
math Supported Includes math.round, math.clamp, and math.log(x, base).
string Supported Includes string.split.
table Supported Includes table.find, table.clear, table.clone.
os Supported os.clock is a high-resolution wall clock; os.date follows modern Luau.
coroutine Supported
debug Partial A restricted library is registered; some stock Lua debug APIs are unavailable.
buffer Not yet Use strings/tables, or wait for the backport.
vector Not yet Use Vector3/Vector2.
package Not loaded Use ModuleScript and require.

The task library is the preferred scheduler API. The legacy wait, spawn, and delay globals still exist for old code.

task.spawn(function()
    print("runs immediately")
end)

task.delay(2, function()
    print("runs after two seconds")
end)

local elapsed = task.wait(1)

Globals and services

The usual Roblox globals are present:

local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local camera = Workspace.CurrentCamera

print(workspace, game, script, typeof(player), player:IsA("Player"))

Instance.new, Enum, Vector3, CFrame, Color3, UDim2, TweenInfo, BrickColor, Ray, Region3, and the other common constructors are available. See the datatype index.

Instance helpers

Attributes, tags, and property-change signals are supported:

local part = workspace:WaitForChild("Door")

part:SetAttribute("Locked", true)
print(part:GetAttribute("Locked"))

part:AddTag("Interactable")
print(part:HasTag("Interactable"))

part:GetPropertyChangedSignal("Anchored"):Connect(function()
    print("Anchored is now", part.Anchored)
end)

CollectionService is the supported way to work with tags across scripts:

local CollectionService = game:GetService("CollectionService")

for _, instance in CollectionService:GetTagged("Interactable") do
    print(instance:GetFullName())
end

CollectionService:GetInstanceAddedSignal("Interactable"):Connect(function(instance)
    print("added", instance)
end)

Security and sandboxing

Nova keeps the classic security model. A normal game script cannot call APIs marked:

  • PluginSecurity — Studio plugins only.
  • LocalUserSecurity — local user / CoreScript contexts.
  • RobloxScriptSecurity — Roblox/CoreScript internals.
  • RobloxSecurity — engine internals.

Trying to call one produces a message such as Insufficient permissions. The API reference marks restricted members with their security level; treat them as unavailable unless you are writing a plugin or CoreScript.

HttpService.HttpEnabled is LocalUserSecurity: the deployment host enables HTTP for game servers. If HttpService:GetAsync says HTTP is not enabled, that is a host configuration issue, not a script bug.

loadstring exists but is sandboxed. getfenv/setfenv are legacy and restricted. Prefer ModuleScript and explicit parameters.

Modules

require works with ModuleScript instances:

-- ReplicatedStorage/Modules/MathUtil
local MathUtil = {}

function MathUtil.clamp01(value: number): number
    return math.clamp(value, 0, 1)
end

return MathUtil
local MathUtil = require(game:GetService("ReplicatedStorage"):WaitForChild("Modules"):WaitForChild("MathUtil"))
print(MathUtil.clamp01(1.5))

Keep shared modules in ReplicatedStorage, server-only modules in ServerScriptService or ServerStorage, and client-only modules in StarterPlayerScripts.

Error handling

pcall and xpcall work as expected. Use them around network calls, data-store calls, and anything that can fail because an asset or instance is missing:

local ok, result = pcall(function()
    return game:GetService("DataStoreService"):GetDataStore("Example"):GetAsync("key")
end)

if not ok then
    warn("data store read failed:", result)
end

Common language differences from old Roblox code

Old pattern Modern / Nova pattern
wait(1) task.wait(1)
spawn(f) task.spawn(f)
delay(1, f) task.delay(1, f)
findFirstChild(name) FindFirstChild(name)
IsPlaying Playing
Pitch PlaybackSpeed
MinDistance / MaxDistance RollOffMinDistance / RollOffMaxDistance
game.Workspace workspace or game:GetService("Workspace")
for _, v in next, t for _, v in t (Luau) or for _, v in pairs(t)

Legacy lowercase aliases usually still exist, but new code should use the modern names.

Editor support

NovaStudio uses the Luau parser for analysis and autocomplete. If a script shows a warning that looks wrong, check whether the construct is actually valid in the Luau VM first; the analyzer and the VM share the same grammar.

Next