Rive Scripting Quick Reference
See Script Capability Matrix for a comprehensive table of script inputs, callback parameters, and hard limitations by script type.
See Runtime Compatibility Baseline for direct
Web 2.42.0 -> C++ runtime-v0.1.344, the separate landing wrapper lane, and
editor/GPU tiers.
Web 2.42.0 requires the experimental host option
enableGPUCanvas: true when importing GPU Canvas content. It defaults to
false, is fixed at RiveFile import, and is not a Luau global. LERP does not
claim that RAV enables it; validate the exported file in each host.
Use the Rive Luau VS Code extension or the Rive Luau LSP when editing .luau files in VS Code, Cursor, Windsurf, or another LSP-capable editor. It provides Rive-parity diagnostics, autocomplete, hover docs, and IntelliSense before final validation in the Rive editor.
For command-line analysis, place every analyzer option before the first path:
./bin/rive/rive-luau-analyze --formatter=plain path/to/script.luau
For an LSP client, configure ./bin/rive/rive-luau-lsp itself as the stdio
command. Do not append another lsp subcommand to the wrapper.
Node Script Skeleton
export type MyNode = {
speed: Input<number>,
enabled: Input<boolean>,
path: Path,
paint: Paint,
}
function init(self: MyNode, context: Context): boolean
self.path = Path.new()
self.paint = Paint.new()
return true
end
function update(self: MyNode)
end
function advance(self: MyNode, seconds: number): boolean
return true
end
function draw(self: MyNode, renderer: Renderer)
end
return function(): Node<MyNode>
return {
init = init,
update = update,
advance = advance,
draw = draw,
speed = 1,
enabled = true,
path = late(),
paint = late(),
}
end
Canvas/GPU work inside draw
function draw(self: MyNode, renderer: Renderer)
-- Pair Canvas:beginFrame/endFrame or GPUCanvas:beginRenderPass/finish.
-- Composite canvas.image or gpuCanvas.image with renderer here.
end
Lifecycle Order
init(self, context)- once on startupdate(self)- when inputs change (NO context param!)advance(self, seconds)- every animation framedraw(self, renderer)- records optional offscreen Canvas/GPUCanvas work and composites on repaint
Runtime Guardrails
- Use pure Luau + Rive APIs; do not use Roblox runtime globals (
game,workspace,Instance). - Script names are flat (no folders/slashes, no
.lua/.luauextension). - Keep long-running integer-style values 32-bit safe (
-2147483648..2147483647).
Inputs & Data Binding
Define Inputs
export type MyNode = {
size: Input<number>,
tint: Input<Color>,
model: Input<Data.Character>,
enemy: Input<Artboard<Data.Enemy>>,
}
return function(): Node<MyNode>
return {
init = init,
draw = draw,
size = 80,
tint = Color.rgb(255, 120, 80),
model = late(),
enemy = late(),
}
end
Read Input Values
local value = self.size
self.tint -- Color
ViewModel and Artboard Inputs
-- ViewModel inputs expose Property<T> fields that use .value
self.model.health.value
self.enemy:instance()
ViewModel Access (Main Artboard)
function init(self: MyNode, context: Context): boolean
local vm = context:viewModel()
local score = vm and vm:getNumber("score")
if score then
print(score.value)
end
return true
end
Global ViewModels
function init(self: MyNode, context: Context): boolean
for _, name in context:globalViewModelNames() do
local global = context:globalViewModel(name)
if global then
print("Global ViewModel:", name)
end
end
return true
end
Unknown or non-global names return nil. Context.log is a current editor
function property called as context.log("message"), but it is absent from the
exact target's C++ Luau Context; use print(...) for Web 2.42.0 portability.
PropertyList Patterns
local items = vm and vm:getList("items")
if items then
items:clear()
local nextItem = Data.Item.new()
items:push(nextItem)
for i = 1, items.length do
local item = items[i]
end
if items.length >= 1 then
items:removeAt(1) -- 1-based
end
end
ListenerContext Safe Payload Branching
function performAction(self: MyAction, listenerContext: ListenerContext)
if listenerContext:isGamepadEvent() then
local g = listenerContext:asGamepadEvent()
if g then
print(g.changeKind, g.changeIndex, g.changeValue)
end
elseif listenerContext:isGamepadConnected() then
local g = listenerContext:asGamepadConnected()
if g then print(g.deviceId, g.mapping, g.leftStick) end
elseif listenerContext:isGamepadDisconnected() then
local g = listenerContext:asGamepadDisconnected()
if g then print(g.deviceId) end
end
end
Current Editor event names are KeyboardEvent, TextInput, FocusEvent,
ReportedEvent, ViewModelChange, NoneEvent, GamepadConnected,
GamepadEvent, and GamepadDisconnected. Older *Invocation names belong to
pinned runtime-source compatibility, not new Editor-authored examples.
FileFormat editor extension
local format: TextFileFormat = {
name = "Notes",
extensions = { "note" },
}
return function(): TextFileFormat
return format
end
This is an editor protocol, not an artboard runtime script. Claimed text files
export as Blob assets; runtime scripts use context:blob(name). See
File Formats for analysis, view, and EditorContext
callbacks.
Promise / async / await
local run = async(function()
local ok, decoded = await(context:decodeImage(self.imageBlob))
if ok then
print(decoded.width, decoded.height)
else
print("decode failed:", decoded)
end
end)
run:catch(function(err) print(err) end)
Drawing Essentials
Path
local path = Path.new()
path:moveTo(Vector.xy(0, 0))
path:lineTo(Vector.xy(100, 0))
path:lineTo(Vector.xy(100, 100))
path:close()
If a path uses a fill, ensure the path is closed and wound clockwise (y points down) — counter-clockwise contours render invisible in the current renderer.
Paint
local fill = Paint.with({ style = "fill", color = Color.rgb(80, 170, 255) })
local stroke = Paint.with({ style = "stroke", thickness = 4, color = Color.rgb(30, 60, 120) })
Renderer
renderer:drawPath(path, fill)
renderer:save()
renderer:transform(Mat2D.withTranslation(50, 0))
renderer:drawPath(path, stroke)
renderer:restore()
Mat4 Quick Use
local model = Mat4.fromTranslation(0, 0, -4) * Mat4.fromRotationY(math.rad(20))
local x, y, z, w = model:transformVec4(1, 0, 0, 1)
GPU Shader Quick Use
function init(self: ShaderNode, context: Context): boolean
self.gpuCanvas = context:gpuCanvas({ width = 512, height = 512 })
self.imageSampler = ImageSampler("clamp", "clamp", "bilinear")
local shader = context:shader("my_effect")
if not shader then
print("Missing shader asset")
return true
end
self.pipeline = GPUPipeline.new({
vertex = { module = shader, entryPoint = "vsMain" },
fragment = { module = shader, entryPoint = "fsMain" },
vertexLayout = self.vertexLayout,
colorTargets = {{ format = self.gpuCanvas.format }},
})
return true
end
function draw(self: ShaderNode, renderer: Renderer)
if not self.pipeline or self.gpuCanvas.width == 0 then return end
local pass = self.gpuCanvas:beginRenderPass({
color = {{ loadOp = "clear", storeOp = "store", clearColor = { 0, 0, 0, 0 } }},
})
pass:setPipeline(self.pipeline)
pass:setVertexBuffer(0, self.vertexBuffer)
pass:draw(self.vertexCount)
pass:finish()
if self.gpuCanvas.width > 0 then
renderer:drawImage(self.gpuCanvas.image, self.imageSampler, "srcOver", 1)
end
end
Luau Essentials Used in Rive
Tables (Arrays + Dictionaries)
local list = {1, 2, 3}
local dict = {x = 10, y = 20}
for i, v in ipairs(list) do end
for k, v in pairs(dict) do end
Functions and Methods
local function clamp(value: number, min: number, max: number): number
return math.max(min, math.min(max, value))
end
function MyType:methodName()
end
String Interpolation
print(`Score: {score}`)
Next Steps
- Continue to Glossary
- Verify live API status in Runtime Compatibility Baseline
- Return to Welcome to LERP