Converter Script Protocol
Learning Objectives
- Understand when to use Converter Scripts
- Implement
convert()for source-to-target transformation - Implement
reverseConvert()for two-way bindings - Work with DataValue types for type-safe conversions
AE/JS Syntax Comparison
| Concept | JavaScript | Luau (Rive Converter Scripts) |
|---|---|---|
| Data transformation | Custom getter/setter | convert() / reverseConvert() |
| Type checking | typeof value === 'number' | input:isNumber() |
| Type casting | Number(value) | (input :: DataValueNumber).value |
| Creating typed values | { type: 'number', value: 42 } | DataValue.number() with .value = 42 |
Converter Scripts are bidirectional transformers. Unlike JavaScript where you might write separate functions for each direction, Rive requires both convert() AND reverseConvert() to handle two-way data bindings properly.
Overview
Converter Scripts enable custom data transformations within Rive's data binding system. They act as a bridge between source and target data with bidirectional conversion.
When to use Converter Scripts:
- Transform data between incompatible types
- Format/parse values (e.g., number to formatted string)
- Apply mathematical transformations to bound values
- Create reusable data transformations across your project
The Converter Script Template
--!strict
export type MyConverter = {
-- Converter state (rarely needed)
}
-- Types of inputs this converter accepts
type DataInputs = DataValueNumber
-- Data type this converter outputs
type DataOutput = DataValueNumber
function init(self: MyConverter, context: Context): boolean
-- Initialize any converter state
return true
end
-- Converts value when binding from SOURCE to TARGET
function convert(self: MyConverter, input: DataInputs): DataOutput
local dv: DataValueNumber = DataValue.number()
if input:isNumber() then
dv.value = (input :: DataValueNumber).value * 2 -- Example: double
end
return dv
end
-- Converts value when binding from TARGET to SOURCE (for two-way binding)
function reverseConvert(self: MyConverter, input: DataOutput): DataInputs
local dv: DataValueNumber = DataValue.number()
if input:isNumber() then
dv.value = (input :: DataValueNumber).value / 2 -- Inverse: halve
end
return dv
end
return function(): Converter<MyConverter, DataInputs, DataOutput>
return {
init = init,
convert = convert,
reverseConvert = reverseConvert,
}
end
Type Signature Reference
Converter Protocol Functions
| Function | Signature | Required | Description |
|---|---|---|---|
init | (self: T, context: Context): boolean | No | Initialize converter state |
convert | (self: T, input: DataInputs): DataOutput | Yes | Transform source → target |
reverseConvert | (self: T, input: DataOutput): DataInputs | Yes | Transform target → source |
Factory Return Type
-- Define your input and output types
type DataInputs = DataValueNumber
type DataOutput = DataValueString
return function(): Converter<YourConverterType, DataInputs, DataOutput>
return {
init = init, -- Optional
convert = convert, -- Required
reverseConvert = reverseConvert, -- Required
}
end
DataValue Types
| Type | Check Method | Cast Pattern |
|---|---|---|
DataValueNumber | input:isNumber() | (input :: DataValueNumber).value |
DataValueString | input:isString() | (input :: DataValueString).value |
DataValueBoolean | input:isBoolean() | (input :: DataValueBoolean).value |
DataValueColor | input:isColor() | (input :: DataValueColor).value |
Key Functions
convert(self, input): DataOutput — REQUIRED
Transforms data from source to target. Called whenever the source value changes.
function convert(self: MyConverter, input: DataInputs): DataOutput
local dv = DataValue.number()
if input:isNumber() then
local value = (input :: DataValueNumber).value
dv.value = value * 100 -- Convert to percentage
end
return dv
end
reverseConvert(self, input): DataInputs — REQUIRED
Transforms data from target back to source. Called during two-way binding when the target changes.
function reverseConvert(self: MyConverter, input: DataOutput): DataInputs
local dv = DataValue.number()
if input:isNumber() then
local value = (input :: DataValueNumber).value
dv.value = value / 100 -- Convert from percentage
end
return dv
end
reverseConvert must be the mathematical inverse of convert. If convert multiplies by 2, reverseConvert must divide by 2.
init(self, context): boolean — Optional
One-time initialization when the converter is first used.
function init(self: MyConverter, context: Context): boolean
-- Set up any initial state
return true
end
DataValue Types
Converter Scripts work with typed DataValue wrappers:
| Type | Constructor | Type Check | .value Type |
|---|---|---|---|
DataValueNumber | DataValue.number() | isNumber() | number |
DataValueString | DataValue.string() | isString() | string |
DataValueBoolean | DataValue.boolean() | isBoolean() | boolean |
DataValueColor | DataValue.color() | isColor() | Color |
DataValue API
-- Create new DataValue instances
local numVal = DataValue.number() -- Creates DataValueNumber
local strVal = DataValue.string() -- Creates DataValueString
local boolVal = DataValue.boolean() -- Creates DataValueBoolean
local colorVal = DataValue.color() -- Creates DataValueColor
-- Type checking (call on any DataValue)
if input:isNumber() then
-- Safe to cast and access .value
end
-- Type casting and value access
local num = (input :: DataValueNumber).value -- Get number
local str = (input :: DataValueString).value -- Get string
local bool = (input :: DataValueBoolean).value -- Get boolean
local color = (input :: DataValueColor).value -- Get Color
How to Apply Converters
- Create a new script, select Converter as the type
- In the Data panel, click the "+" button
- Choose Converters → Script → [YourScript]
- Apply to bindings in the editor
Practice Exercises
Exercise 1: Percentage Converter ⭐
Premise
Converters transform data between formats. Understanding the relationship between convert() and reverseConvert() is essential for two-way bindings.
By the end of this exercise, you will implement a converter that transforms a decimal (0.75) to a percentage (75).
Use Case
You have a progress value stored as a decimal (0.0 to 1.0), but you want to display it as a percentage (0 to 100) in the UI.
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Converter Script - Name it
Exercise1_PercentConverter
- Assets panel →
-
Test setup:
- Create a ViewModel with a
progressnumber property - Bind it to a text element using your converter
- Create a ViewModel with a
Starter Code
--!strict
export type Exercise1 = {}
type DataInputs = DataValueNumber
type DataOutput = DataValueNumber
function convert(self: Exercise1, input: DataInputs): DataOutput
local dv = DataValue.number()
if input:isNumber() then
local decimal = (input :: DataValueNumber).value
-- TODO: Convert decimal to percentage
-- Formula: percentage = decimal * 100
local percentage = 0
dv.value = percentage
print(`ANSWER: convert={percentage}`)
end
return dv
end
function reverseConvert(self: Exercise1, input: DataOutput): DataInputs
local dv = DataValue.number()
if input:isNumber() then
local percentage = (input :: DataValueNumber).value
-- TODO: Convert percentage back to decimal
-- Formula: decimal = percentage / 100
local decimal = 0
dv.value = decimal
end
return dv
end
return function(): Converter<Exercise1, DataInputs, DataOutput>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end
Assignment
- Calculate
percentageasdecimal * 100 - Calculate
decimalaspercentage / 100 - Test with Input decimal of 0.75
Expected Output
Your console output should display the converted percentage value.
Verify Your Answer
Checklist
-
convert()multiplies by 100 -
reverseConvert()divides by 100 - Functions are mathematical inverses
Exercise 2: Temperature Converter ⭐⭐
Premise
Real-world converters often involve non-trivial formulas. Temperature conversion is a classic example of bidirectional transformation with offsets.
By the end of this exercise, you will implement Celsius to Fahrenheit conversion with proper inverse operations.
Use Case
Your app stores temperatures in Celsius internally, but displays Fahrenheit for US users.
Setup
In Rive Editor:
- Create the script:
- Assets panel →
+→ Script → Converter Script - Name it
Exercise2_TempConverter
- Assets panel →
Starter Code
--!strict
export type Exercise2 = {}
type DataInputs = DataValueNumber
type DataOutput = DataValueNumber
function convert(self: Exercise2, input: DataInputs): DataOutput
local dv = DataValue.number()
if input:isNumber() then
local celsius = (input :: DataValueNumber).value
-- TODO: Convert Celsius to Fahrenheit
-- Formula: F = C * 9/5 + 32
local fahrenheit = 0
dv.value = fahrenheit
print(`ANSWER: celsius={celsius},fahrenheit={fahrenheit}`)
end
return dv
end
function reverseConvert(self: Exercise2, input: DataOutput): DataInputs
local dv = DataValue.number()
if input:isNumber() then
local fahrenheit = (input :: DataValueNumber).value
-- TODO: Convert Fahrenheit back to Celsius
-- Formula: C = (F - 32) * 5/9
local celsius = 0
dv.value = celsius
end
return dv
end
return function(): Converter<Exercise2, DataInputs, DataOutput>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end
Assignment
- Implement the Celsius to Fahrenheit formula:
F = C * 9/5 + 32 - Implement the inverse:
C = (F - 32) * 5/9 - Test with input of 25 Celsius
Expected Output
Your console output should display both the input Celsius and converted Fahrenheit values.
Verify Your Answer
Checklist
- Fahrenheit formula correct (C * 9/5 + 32)
- Celsius formula correct ((F - 32) * 5/9)
- Formulas are mathematical inverses
Exercise 3: String Formatter ⭐⭐
Premise
Converters can transform between different types. Converting numbers to formatted strings is common for display purposes.
By the end of this exercise, you will implement a converter that formats a number with a currency prefix.
Use Case
You have a price stored as a number (19.99), but need to display it as "$19.99" in the UI.
Setup
In Rive Editor:
- Create the script:
- Assets panel →
+→ Script → Converter Script - Name it
Exercise3_CurrencyFormatter
- Assets panel →
Starter Code
--!strict
export type Exercise3 = {}
type DataInputs = DataValueNumber
type DataOutput = DataValueString
function convert(self: Exercise3, input: DataInputs): DataOutput
local dv = DataValue.string()
if input:isNumber() then
local price = (input :: DataValueNumber).value
-- TODO: Format as currency string
-- Use string interpolation: `$amount`
local formatted = ""
dv.value = formatted
print(`ANSWER: formatted={formatted}`)
end
return dv
end
function reverseConvert(self: Exercise3, input: DataOutput): DataInputs
local dv = DataValue.number()
if input:isString() then
local str = (input :: DataValueString).value
-- TODO: Parse number from currency string
-- Remove "$" prefix and convert to number
-- Hint: Use string.sub(str, 2) to remove first character
local price = 0
dv.value = price
end
return dv
end
return function(): Converter<Exercise3, DataInputs, DataOutput>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end
Assignment
- Format the number as
$followed by the price - Parse by removing the
$and converting withtonumber() - Test with input price of 42
Expected Output
Your console output should display the formatted currency string.
Verify Your Answer
Checklist
-
convert()prepends "$" to number -
reverseConvert()strips "$" and parses number - Note: DataInputs is Number, DataOutput is String
Knowledge Check
Common Mistakes
-
Forgetting reverseConvert is required
-- WRONG: Missing reverseConvert
return function(): Converter<MyConverter, DataInputs, DataOutput>
return { convert = convert } -- Error!
end
-- CORRECT: Always include both
return function(): Converter<MyConverter, DataInputs, DataOutput>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end -
Non-inverse operations
-- WRONG: Operations don't reverse properly
function convert(self, input)
-- Adds 10, but reverseConvert subtracts 5 = BROKEN!
end
-- CORRECT: Mathematical inverses
function convert(self, input)
-- result = value * 2 + 10
end
function reverseConvert(self, input)
-- value = (result - 10) / 2
end -
Not checking input type
-- WRONG: Accessing .value without type check
local value = (input :: DataValueNumber).value -- Could crash!
-- CORRECT: Always check first
if input:isNumber() then
local value = (input :: DataValueNumber).value -- Safe
end
Self-Assessment Checklist
- I understand when to use Converter Scripts
- I can implement convert() for source→target transformation
- I can implement reverseConvert() as the mathematical inverse
- I know how to use DataValue types and type checking
- I understand the relationship between convert and reverseConvert
Next Steps
- Continue to Path Effect Script Protocol
- Need a refresher? Review Quick Reference