Skip to content

Functions runtime

Each function a module declares is a handler registered in its module.wasm. The backend runs it in a wazero WebAssembly sandbox with no network, filesystem or environment access.

Every invocation runs inside one Postgres transaction. A handler that returns a result commits. A handler that returns an error, crashes, runs out of time or exceeds the memory limit rolls everything back. Kernel calls are synchronous.

Event handlers run in a savepoint: a failing handler is logged and does not fail the operation that emitted the event.

These limits are enforced by the host and cannot be configured:

  • 200 ms per client, admin and event invocation, 5 s per schedule invocation. The budget includes nested module calls.
  • 128 MB of memory per instance.
  • Nested module call depth of 8.
  • Console output (stdout and stderr) capped at 64 KB per invocation.
TriggerInvoked by
clienta player: POST /sdk/v1/modules/{module}/{fn}
adminthe dashboard (POST /platform/v1/projects/{projectId}/modules/{module}/{fn}) or a server with the admin key (POST /game/v1/modules/{module}/{fn}); roles limits which roles may call it
eventthe kernel, when the event named in event is emitted
schedulethe cron worker, on the function’s cron expression
moduleanother module that lists this module in its deps

A schedule function must have a valid 5-field cron expression in cron (minute, hour, day of month, month, day of week, in UTC). Fields accept *, numbers, ranges (1-5), lists (1,15) and steps (*/15). Publishing or dry-running a package with a missing or invalid cron fails with HTTP 400.

Schedules run in both environments, staging and prod, for every enabled install. The worker checks once a minute. A run that was missed (for example during a deploy) is caught up on the next check, looking back at most 35 days.

Function outcomeTransactionPlayer response
returns a resultcommit200 { result }
returns a business error (Fail(code, message))rollback422 { error, code, requestId } with the module’s code
crash, panic, no outputrollback500 without details
timeoutrollback504 without details
memory limitrollback500 without details

Module error codes are strings chosen by the module author and declared with rudder.Errors(...). They go into the manifest, and generated module clients expose them as a union type (TypeScript) or a constants class (C#). A module that returns an undeclared code still gets it to the player, and the backend writes a warning to the module log.

Client calls are idempotent: the SDKs send an Idempotency-Key, and the backend stores the response for 24 hours and returns it for a repeated key. A key reused for another function fails with 422 idempotency_key_mismatch.

If a nested module call fails, the whole invocation fails and the player receives the failure of the module that was called.

Failures and console output are written to the module log (dashboard Modules → Logs, kept 7 days).

The dashboard and rudder module dry-run run a function with dryRun: true. Dry run is available only to allowlisted publisher accounts (early access). A dry run always rolls back and returns result, error, console, log (the kernel calls made) and events. The player endpoint never returns these fields.

Each SDK wraps the same host operations. The project, environment and calling player come from the invocation context; a module cannot pass them.

AreaOperations
Walletget, adjust (a negative amount debits; insufficient funds fails)
Storageplayer storage get, set
Project storageget, set
Countersget, increment (emits counter.incremented for positive deltas)
Resourceslist, get, create, update (optional optimistic version), delete, count
Segmentsmatch (player against a segment), bucket (stable player bucket for a salt)
Notificationssend
Eventsemit
Modulescall (target must be in deps and have trigger module)

The context also carries the trigger, the player (for client calls), the module config values, the module slug, the environment and the current time.

Kernel events: counter.incremented, player.created, wallet.adjusted, storage.updated. Custom events use <module>.<name>; names starting with counter., player., wallet. or storage. are reserved.

Modules are written in Go with hub.rudder.build/rudder/rudder-module-go. Other languages are not supported for now.

Go handlers must be registered in init(), not main():

func init() {
rudder.Register("hello", rudder.Typed(hello), rudder.Errors("name_required"))
}
type helloArguments struct {
Name string `json:"name" rudder:"maxLength=64"`
}
type helloResult struct {
Message string `json:"message"`
}
func hello(ctx *rudder.Context, args helloArguments) (helloResult, error) {
if args.Name == "" {
return helloResult{}, rudder.Fail("name_required", "name is required")
}
return helloResult{Message: "hello " + args.Name}, nil
}

rudder.Typed records the argument and result types; rudder module build turns them into the function’s JSON Schemas (see Module manifest).

In the module SDK, optional scalar fields of kernel calls use omitempty, so an explicit 0, false or "" is not sent. For every such field the zero value is also the default. For example, Resources.Update with Version: 0 updates without a version check.