Skip to content

Generated module clients

Game features are modules. The client SDKs do not know any module; instead, the rudder CLI reads the manifests of the modules installed in an environment and writes one typed client file for your game.

The rudder CLI (0.1.0) ships as binaries for macOS, Linux and Windows (amd64 and arm64) on the Gitea releases page of the liveops-module-sdk repository. Download the binary for your platform and put it on your PATH.

Terminal window
export RUDDER_URL=https://api.rudder.build
export RUDDER_TOKEN=... # personal access token (Account Settings in the dashboard)
export RUDDER_PROJECT=... # project id
rudder client generate --lang ts --out src/rudder.modules.ts
rudder client generate --lang csharp --out Assets/Rudder/RudderModules.g.cs
FlagMeaning
--lang ts|csharpOutput language.
--out <file>Output path. Defaults to rudder.modules.ts or RudderModules.g.cs.
--environment staging|prodWhich environment’s installed modules to read. Default staging.
--manifests <dir>Read <dir>/*.json and <dir>/*/dist/manifest.json instead of the server.
  • Only functions with trigger client are generated.
  • The file header lists every module with its version.
  • The file is a factory over the SDK client; nothing is patched into the SDK.
  • Commit the file and regenerate it after installing, upgrading or removing modules. Do not edit it by hand. There is no editor integration: Unity projects run the CLI too.
import { createClient } from '@rudder/sdk';
import { modules } from './rudder.modules';
const client = createClient({ projectKey });
await client.auth.loginWithDevice();
const m = modules(client);
await m.leaderboards.submit({ slug: 'weekly', score: 120 });
const { entries } = await m.leaderboards.top({ slug: 'weekly', limit: 10 });

The file exports:

  • modules(client): { <module>: { <fn>(args): Promise<Result> } }, with module and function names in camelCase (remote_configremoteConfig);
  • <Module><Fn>Args and <Module><Fn>Result interfaces (nested types are named by path) and string literal unions for enums;
  • <Module><Fn>Error: the function’s declared error codes plus RudderKernelError.

Each generated function calls the SDK’s modules.call / Modules.CallAsync, which sends POST /sdk/v1/modules/{module}/{fn} with { args } and returns result.

  • Idempotency. The SDK generates one Idempotency-Key per call and sends the same key on every retry. The backend stores the response for 24 hours and returns it for a repeated key, so a retried call runs at most once. A key reused for another module or function fails with 422 idempotency_key_mismatch.
  • Retries. Up to 2, with jittered backoff, on network errors, 5xx and 409 conflict. Other 4xx responses are never retried.
  • Version header. Generated clients send X-Rudder-Module-Version: <module>@<version> with the version the file was generated from. When the major version differs from the installed one, the call still runs and the backend writes a warning to the module log (Modules → Logs). Regenerate the client when you see it.

A module that fails a call on purpose returns HTTP 422 { error, code, requestId }. The SDKs turn it into a typed error:

import { RudderModuleError } from '@rudder/sdk';
import { modules, type StoreBuyError } from './rudder.modules';
try {
await modules(client).store.buy({ offerSlug: 'starter_pack' });
} catch (error) {
if (error instanceof RudderModuleError) {
switch (error.code as StoreBuyError) {
case 'insufficient_funds':
showNotEnoughFunds();
break;
case 'purchase_limit_reached':
showSoldOut();
break;
}
}
}

The error sets come from the module: codes declared with rudder.Errors(...) (see Module manifest) plus the kernel codes such as invalid_parameters, forbidden, conflict and insufficient_funds. A module crash is a 500 and a timeout a 504, both without details.

remote_config.get returns { values: { [key]: { type, value } } }, where type is string, number, bool or json and value is the value as text. When remote_config is installed, the generated file adds helpers that parse a value by key and return nothing for a missing key or another type:

import { modules, remoteConfigBool, remoteConfigJson, remoteConfigNumber } from './rudder.modules';
const config = await modules(client).remoteConfig.get({});
const multiplier = remoteConfigNumber(config, 'reward_multiplier') ?? 1;
const newShop = remoteConfigBool(config, 'new_shop_enabled') ?? false;
const tuning = remoteConfigJson<{ spawnRate: number }>(config, 'tuning');

Helpers: remoteConfigString, remoteConfigNumber, remoteConfigBool, remoteConfigJson<T>; each returns undefined when the key is missing or has another type.

See Feature Flags for a full example.