Type Registry

The type registry controls how Go types are converted to and from Risor types. It is used when passing Go values into scripts and when returning results. The registry is optional (Risor handles common types automatically), but it enables precise control over custom type conversions.

Creating a Registry

Use risor.NewTypeRegistry() to start building a registry, register your conversions, and call Build() to finalize it.

registry := risor.NewTypeRegistry().
    RegisterFromGo(reflect.TypeOf(User{}), func(v any) (object.Object, error) {
        u := v.(User)
        return object.NewMap(map[string]object.Object{
            "id":   object.NewInt(int64(u.ID)),
            "name": object.NewString(u.Name),
        }), nil
    }).
    Build()

Then pass the registry when evaluating scripts:

result, err := risor.Eval(ctx, source,
    risor.WithEnv(risor.Builtins()),
    risor.WithTypeRegistry(registry),
)

RegisterFromGo

RegisterFromGo registers a conversion function that transforms a Go value into a Risor object.Object. The first argument is a reflect.Type that identifies which Go type this conversion handles. The second argument is the conversion function.

registry := risor.NewTypeRegistry().
    RegisterFromGo(reflect.TypeOf(Order{}), func(v any) (object.Object, error) {
        o := v.(Order)
        return object.NewMap(map[string]object.Object{
            "id":    object.NewInt(int64(o.ID)),
            "total": object.NewFloat(o.Total),
        }), nil
    }).
    Build()

When a Go value of the registered type is passed into a script environment, the registry's conversion function is called automatically.

GoFunc

GoFunc wraps an arbitrary Go function for use in Risor. The function's parameters and return values are converted automatically using reflection and the type registry.

env := risor.Builtins()
env["add"] = func(a, b int) int { return a + b }
 
result, err := risor.Eval(ctx, `add(3, 4)`, risor.WithEnv(env))
// result: 7

When a Go function is placed in the environment, Risor wraps it as a GoFunc internally. The type registry is consulted for any parameter or return types that need conversion beyond the built-in defaults.

GoStruct

GoStruct wraps a Go struct pointer and exposes its exported fields and methods to Risor scripts. Fields are accessible by name and methods can be called directly.

type Service struct {
    Name    string
    Version int
}
 
func (s *Service) Info() string {
    return fmt.Sprintf("%s v%d", s.Name, s.Version)
}
 
svc := &Service{Name: "MyApp", Version: 2}
result, err := risor.Eval(ctx, `svc.Info()`,
    risor.WithEnv(map[string]any{"svc": svc}),
)
// result: "MyApp v2"

Struct field access in scripts uses the exported Go field name:

svc.Name     // "MyApp"
svc.Version  // 2
svc.Info()   // "MyApp v2"

How Conversion Works

Both GoFunc and GoStruct rely on the type registry to convert values between Go and Risor types. The conversion flow is:

  1. Go to Risor. When a Go value enters the script environment, the registry checks for a registered FromGo conversion. If none is found, default conversions are applied (strings, numbers, bools, slices, maps). Struct pointers are wrapped as GoStruct and functions as GoFunc.

  2. Risor to Go. When a script calls a Go function or sets a struct field, the Risor object is converted back to the expected Go type using the registry or default conversions.

Custom registry conversions take priority over the defaults, giving you full control over how your domain types appear in scripts.


Risor © 2026 Deep Noodle