Rive Scripting Quick Reference
Need the full script matrix?
See Script Capability Matrix for a comprehensive table of script inputs, callback parameters, and hard limitations by script type.
Need version-locked API status?
See Runtime Compatibility Baseline for the current C++ WASM runtime release line (2.37.8), npm source pin (bc560112), source-level API snapshot (runtime-v0.1.106), and shader/GPU Early Access tier.
Writing scripts outside Rive?
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.
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
Optional GPU/Canvas Phase
function drawCanvas(self: MyNode)
-- Canvas:beginFrame(...) or GPUCanvas:beginRenderPass(...) work
end
function draw(self: MyNode, renderer: Renderer)
-- Composite canvas.image or gpuCanvas.image here
end
Lifecycle Order
init(self, context)- once on startupdate(self)- when inputs change (NO context param!)advance(self, seconds)- every animation framedrawCanvas(self)- optional offscreen Canvas/GPUCanvas render phasedraw(self, renderer)- on canvas repaint (keep pure, no state changes!)
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
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:isGamepad() then
local g = listenerContext:asGamepad()
if g then
print(g.deviceId, g.buttonMask, g.axis0)
end
end
end
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.
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 drawCanvas(self: ShaderNode)
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()
end
function draw(self: ShaderNode, renderer: Renderer)
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