Skip to main content

Data & Input

APIs for script inputs, properties, runtime context, and data binding.


Input

An input is a value on your script that appears as a control in the Rive editor's inspector panel. When someone changes that control, your script receives the new value automatically.

Think of inputs like knobs and sliders on a mixing board — they let you (or someone using your file) adjust behavior without editing code.

How Inputs Work

  1. You declare an input in your script's type definition
  2. You provide a default value in the factory return
  3. The input appears in the editor as a control (slider, checkbox, color picker, etc.)
  4. When the value changes, Rive calls your script's update() function
  5. You read the value with self.inputName — no .value needed
export type MyNode = {
speed: Input<number>, -- declares an input called "speed"
}

function update(self: MyNode)
-- This runs whenever speed changes in the editor
print("Speed is now: " .. self.speed)
end

return function(): Node<MyNode>
return {
init = init,
update = update,
draw = draw,
speed = 50, -- default value: 50
}
end
Inputs vs Properties

Inputs (Input<T>) are read with self.speed — no .value. Properties (Property<T>) from ViewModels are read with prop.value. They look similar but work differently. If you're using self.something and it's an input, just use it directly.


Input<number>

A numeric value. Appears as a number field in the editor.

When to use: Anything that's a number — speed, size, count, opacity, angle, duration.

Factory default: Any number (e.g. 42, 0.5, 100).

How to read: self.speed returns the number directly.

export type Particle = {
speed: Input<number>,
size: Input<number>,
opacity: Input<number>,
}

function advance(self: Particle, seconds: number): boolean
-- Use the numbers directly
local distance = self.speed * seconds
local radius = self.size * 0.5
return true
end

return function(): Node<Particle>
return {
init = init,
advance = advance,
draw = draw,
speed = 100, -- pixels per second
size = 20, -- diameter in pixels
opacity = 255, -- fully opaque
}
end

Input<boolean>

A true/false toggle. Appears as a checkbox in the editor.

When to use: On/off switches — show/hide something, enable/disable a feature, flip a mode.

Factory default: true or false.

How to read: self.isVisible returns true or false.

export type Overlay = {
isVisible: Input<boolean>,
showShadow: Input<boolean>,
}

function draw(self: Overlay, renderer: Renderer)
if not self.isVisible then return end

renderer:drawPath(self.bgPath, self.bgPaint)

if self.showShadow then
renderer:drawPath(self.shadowPath, self.shadowPaint)
end
end

return function(): Node<Overlay>
return {
init = init,
draw = draw,
isVisible = true,
showShadow = false,
}
end

Input<string>

A text value. Appears as a text field in the editor.

When to use: Labels, names, mode selectors, anything that's text.

Factory default: Any string in quotes (e.g. "hello", "idle", "").

How to read: self.label returns the string directly.

export type Badge = {
label: Input<string>,
mode: Input<string>,
}

function update(self: Badge)
if self.mode == "warning" then
self.paint.color = Color.rgb(255, 200, 0)
elseif self.mode == "error" then
self.paint.color = Color.rgb(255, 60, 60)
else
self.paint.color = Color.rgb(100, 200, 100)
end
end

return function(): Node<Badge>
return {
init = init,
update = update,
draw = draw,
label = "Status",
mode = "normal",
}
end

Input<Color>

A color value. Appears as a color picker in the editor.

When to use: Tints, fills, backgrounds — any color that should be adjustable.

Factory default: A Color value like Color.rgb(255, 0, 0) or Color.rgba(100, 150, 200, 128).

How to read: self.tint returns a Color value you can assign to a paint.

export type ColoredShape = {
fillColor: Input<Color>,
strokeColor: Input<Color>,
}

function update(self: ColoredShape)
-- When either color changes, update the paints
self.fillPaint.color = self.fillColor
self.strokePaint.color = self.strokeColor
end

return function(): Node<ColoredShape>
return {
init = init,
update = update,
draw = draw,
fillColor = Color.rgb(80, 160, 255),
strokeColor = Color.rgb(40, 80, 180),
fillPaint = late(),
strokePaint = late(),
}
end

Input<Data.X>

A reference to a ViewModel instance — a named collection of properties defined in the Rive editor. This is how you connect your script to data that lives on the artboard.

When to use: When your script needs to read or write properties that are bound to visual elements — positions, scores, colors, toggles that drive the animation.

Factory default: late() — because the editor assigns the ViewModel instance at runtime. Your script doesn't know which instance it will receive until the file loads.

How to read: self.character gives you the ViewModel instance. To access its properties, use .propertyName.value:

export type Controller = {
character: Input<Data.Character>,
time: number,
}

function advance(self: Controller, seconds: number): boolean
self.time += seconds

-- Read a property
local currentX = self.character.posX.value

-- Write a property (updates all bound visual elements)
self.character.posX.value = math.sin(self.time) * 100
self.character.posY.value = math.cos(self.time) * 50

return true
end

return function(): Node<Controller>
return {
init = init,
advance = advance,
draw = draw,
character = late(), -- assigned by the editor
time = 0,
}
end

Editor setup:

  1. Create a ViewModel in the Rive editor (e.g. Character with posX and posY number properties)
  2. Add the script to the artboard so Rive creates a script node
  3. In the script's input panel, assign the ViewModel instance to character
Why .value?

self.character is the ViewModel instance. self.character.posX is a Property<number>. Properties hold their value inside .value — that's how Rive tracks changes and updates bindings. So you always read/write with .value at the end.


Input<Artboard> / Input<Artboard<...>>

A reference to a nested artboard (component). Use this to create instances of reusable components at runtime — particles, list items, repeated UI elements.

The runtime accepts both:

  • Input<Artboard> for untyped component references
  • Input<Artboard<Data.X>> or Input<Artboard<nil>> when you want explicit generic annotations in strict codebases

When to use: When your script needs to stamp out copies of a component, each with its own data.

Factory default: late() — the editor assigns the artboard reference.

How to read: self.template gives you the artboard. Call :instance() to create a copy, optionally with a ViewModel:

export type Spawner = {
template: Input<Artboard<Data.ItemVm>>,
instances: { Artboard<Data.ItemVm> },
}

function init(self: Spawner, context: Context): boolean
self.instances = {}

-- Create 5 instances of the template
for i = 1, 5 do
local vm = Data.ItemVm.new()
local idx = vm:getNumber("index")
if idx then idx.value = i end

local inst = self.template:instance(vm)
inst.frameOrigin = false
table.insert(self.instances, inst)
end

return true
end

return function(): Node<Spawner>
return {
init = init,
advance = advance,
draw = draw,
template = late(), -- assigned by the editor
instances = {},
}
end

Editor setup:

  1. Create a component artboard (the thing you want to repeat)
  2. Add the script to the artboard so Rive creates a script node
  3. In the script's input panel, assign the component to template
No context:artboard(...) helper

Use Input<Artboard<...>> (or Input<Artboard<nil>>) for component references. Do not rely on a context:artboard(name) helper in current runtime docs.


Input<Trigger>

A one-shot signal — like pressing a button. Unlike all other input types, a trigger doesn't hold a value. It fires once and is done.

When to use: Reset buttons, "play" signals, "spawn now" commands — any action that should happen once when activated.

Factory default: function() end — a function that does nothing. This is different from other inputs: triggers must be functions because Rive calls the function directly when the trigger fires. Using late() or nil causes the error "expected trigger X to be a function".

How it works: Rive calls your function the moment the trigger fires. You can override this function in init() to do something useful:

export type Resettable = {
resetTrigger: Input<Trigger>,
pendingReset: boolean,
score: number,
}

function init(self: Resettable, context: Context): boolean
-- Replace the do-nothing placeholder with a real function
self.resetTrigger = function()
self.pendingReset = true
end
return true
end

function advance(self: Resettable, seconds: number): boolean
if self.pendingReset then
self.pendingReset = false
self.score = 0 -- reset the score
end
return true
end

return function(): Node<Resettable>
return {
init = init,
advance = advance,
draw = draw,
resetTrigger = function() end, -- placeholder
pendingReset = false,
score = 0,
}
end

Execution order on a trigger frame: trigger fn → advance()update().

Editor setup:

  1. Add a Trigger property to your ViewModel
  2. In the script's input panel, bind resetTrigger to the ViewModel trigger
  3. Fire it from a state machine, listener, or another script

See Trigger Inputs for a deeper walkthrough of the flag pattern and why it's useful.

Triggers can also be accessed via vm:getTrigger("name") with addListener(callback) — see PropertyTrigger.


Change Handling

When any input changes, Rive calls your update() function. This is where you react to new values — rebuild geometry, update colors, recalculate layout.

function update(self: MyNode)
-- Called whenever any input changes
-- Rebuild whatever depends on the inputs
rebuildShape(self)
end

update() does not tell you which input changed. If you need to know, store the previous value and compare:

function update(self: MyNode)
if self.size ~= self.lastSize then
rebuildPath(self)
self.lastSize = self.size
end
end

See Also: Property, ViewModel


Property

Mutable property with change notification.

Attributes

property.value

The property value. Type: T (read/write)

Methods

property:addListener(callback)

Registers a change callback. Can be called with or without a bound object.

property:addListener(callback: () -> ())
property:addListener(obj: any, callback: (any) -> ())

Listener lifetime note: For long-lived listeners, keep ViewModel/property handles on self (or use the obj/anchor overload) so listener targets are not lost when short-lived locals go out of scope.

property:removeListener(callback)

Removes a callback.

property:removeListener(callback: () -> ())

See Also: Input, ViewModel


Context

Runtime context passed to init() in all script protocols.

Methods

context:viewModel()

Gets the ViewModel bound to the current node's immediate data context.

context:viewModel(): ViewModel?

context:rootViewModel()

Gets the root ViewModel of the artboard data hierarchy.

context:rootViewModel(): ViewModel?

context:dataContext()

Gets the DataContext for hierarchy traversal.

context:dataContext(): DataContext?

context:image(name)

Gets an image asset by name.

context:image(name: string): Image?

context:blob(name)

Gets a blob (binary data) asset by name.

context:blob(name: string): Blob?

context:audio(name)

Gets an audio source asset by name.

context:audio(name: string): AudioSource?

context:canvas(options)

Creates an offscreen 2D render canvas.

Release tier: Officially released (advanced surface)

context:canvas(options: { width: number, height: number, clearColor: Color? }): Canvas

Canvas supports:

  • canvas:beginFrame(options?) -> Renderer
  • canvas:endFrame()
  • canvas:resize(width, height)
  • canvas.image, canvas.width, canvas.height

Lifecycle constraints:

  • beginFrame() and endFrame() must be paired.
  • resize() must not be called between beginFrame() and endFrame().
  • beginFrame() is a draw-phase operation (treat it as render-time, not init-time work).

context:gpuCanvas(options?)

Creates an offscreen GPU canvas handle for advanced GPU scripting workflows.

Release tier: Preview / rollout-dependent

context:gpuCanvas(options: { width: number, height: number }?): GPUCanvas

GPUCanvas supports:

  • gpuCanvas:beginRenderPass(desc) -> GPURenderPass
  • gpuCanvas:colorView() -> GPUTextureView
  • gpuCanvas:resize(width, height)
  • gpuCanvas.image, gpuCanvas.width, gpuCanvas.height, gpuCanvas.format

Use this as preview-only material in LERP. Runtime bindings and docs exist, but editor-side shader/GPU authoring workflows may not be visible in every workspace/build.

For the full GPU object family, descriptor types, render-pass patterns, and shader examples, see GPU Shaders.

context:features()

Returns a GPU capability table (feature flags + limits).

Release tier: Preview / rollout-dependent

context:features(): GPUFeatures

Known fields at this baseline:

  • bc, etc2, astc
  • maxTextureSize2D, maxTextureSizeCube, maxTextureSize3D
  • anisotropicFiltering, texture3D, textureArrays, colorBufferFloat
  • perTargetBlend, perTargetWriteMask, drawBaseInstance, depthBiasClamp
  • maxColorAttachments, maxUniformBufferSize, maxSamplers, maxSamples

context:shader(name)

Gets a compiled shader asset by name, without the .wgsl extension.

Release tier: Preview / rollout-dependent

context:shader(name: string): Shader?

If your editor/workspace does not expose or package shader assets yet, this can legitimately return nil even when other scripting APIs work. New LERP shader examples use context:shader(name) and GPUCanvas.format; do not use older context:loadShader(name) or context:preferredCanvasFormat() references in new material.

context:decodeImage(buffer)

Decodes encoded image bytes asynchronously.

Release tier: Officially released (advanced surface)

context:decodeImage(encodedBytes: buffer): Promise<DecodedImage>

Resolved value type (DecodedImage):

  • data (buffer, premultiplied RGBA8 bytes)
  • width (number)
  • height (number)

GPU object family (preview surface)

The following constructors and object APIs are runtime-bound and documented upstream, but should be treated as preview / rollout-dependent in LERP. See GPU Shaders for signatures, descriptor types, and usage patterns:

  • GPUBuffer.new(...), GPUBuffer:write(...), GPUBuffer.size
  • GPUTexture.new(...), GPUTexture:view(...), GPUTexture:upload(...), GPUTexture.width/height/format
  • GPUSampler.new(...)
  • GPUBindGroupLayout.new(...)
  • GPUBindGroup.new(...)
  • GPUPipeline.new(...), pipeline:getBindGroupLayout(groupIndex)
  • GPURenderPass methods: setPipeline, setVertexBuffer, setIndexBuffer, setBindGroup, setViewport, setScissorRect, setStencilReference, setBlendColor, draw, drawIndexed, finish
  • GPUTextureView.format
  • Image:view() for sampling an image asset as a shader texture

For course-stability policy, see Runtime Compatibility Baseline.

context:markNeedsUpdate()

Requests an update on the next frame.

context:markNeedsUpdate()

See Also: ViewModel, DataContext, Image, Blob, AudioSource, DecodedImage, Runtime Compatibility Baseline


ViewModel

Connects editor elements to data and code. Provides access to bound properties by type.

Runtime baseline note

At the current compatibility baseline (public npm runtime line 2.37.8, source-level API snapshot runtime-v0.1.106, revalidated June 4, 2026), viewModel.name is documented upstream but not exposed in the C++ Luau bindings. Use explicit property lookups (getNumber, getString, etc.) instead of relying on a name field.

Methods

viewModel:getNumber(name)

Gets a numeric property by name.

viewModel:getNumber(name: string): Property<number>?

viewModel:getString(name)

Gets a string property by name.

viewModel:getString(name: string): Property<string>?

viewModel:getBoolean(name)

Gets a boolean property by name.

viewModel:getBoolean(name: string): Property<boolean>?

viewModel:getColor(name)

Gets a color property by name.

viewModel:getColor(name: string): Property<Color>?

viewModel:getTrigger(name)

Gets a trigger property by name.

viewModel:getTrigger(name: string): PropertyTrigger?

viewModel:getList(name)

Gets a list property by name.

viewModel:getList(name: string): PropertyList?

viewModel:getImage(name)

Gets an image property by name.

viewModel:getImage(name: string): Property<Image>?

viewModel:getViewModel(name)

Gets a nested ViewModel by name. Returns a PropertyViewModel wrapper (access the ViewModel via .value).

viewModel:getViewModel(name: string): PropertyViewModel?

viewModel:getEnum(name)

Gets an enum property by name.

viewModel:getEnum(name: string): PropertyEnum?

viewModel:getIndex()

Gets the ViewModel list-item index for list-bound item ViewModels.

viewModel:getIndex(): number

Returns -1 when the ViewModel is not currently list-bound.

viewModel:instance()

Creates an independent instance of the ViewModel.

viewModel:instance(): ViewModel

Example

function init(self: MyScript, context: Context): boolean
self.vm = context:viewModel()
if not self.vm then
return false
end

-- Get typed properties
self.score = self.vm:getNumber("score")
self.playerName = self.vm:getString("playerName")
self.isActive = self.vm:getBoolean("isActive")

-- Read values
if self.score then
print(`Score: {self.score.value}`)
end

-- Listen for changes
if self.playerName then
self.playerName:addListener(function()
print(`Name changed to: {self.playerName.value}`)
end)
end

return true
end

See Also: Property, Context, PropertyTrigger


DataContext

Provides hierarchy traversal for data binding contexts. Obtained via context:dataContext().

Methods

dataContext:parent()

Gets the parent DataContext, or nil if at the root.

dataContext:parent(): DataContext?

dataContext:viewModel()

Gets the ViewModel associated with this context level.

dataContext:viewModel(): ViewModel?

See Also: Context, ViewModel

Next Steps