Scripts


What is script?

An script is a custom endpoint that extends the functionality of your game backend. It allows you to define server-side logic that can be called by your game clients or other scripts.

Key features of scripts include:

  • Defined request and response types: Every script has a clear contract for the data it expects to receive (request) and the data it will return (response). This enables automatic validation and helps catch bugs early.

  • Public or internal access: Scripts can be marked as public (callable by game clients via SDK endpoints) or internal (only accessible within the backend).

  • Auto-generated SDK endpoints and docs: For public scripts, Rivet automatically generates SDK endpoints and documentation, making it easy for game clients to interact with your custom functionality.

  • Access to backend resources: Scripts have access to the ScriptContext object which allows them to call other modules, interact with databases, read configs and environment variables.

Scripts provide a powerful and flexible way to implement custom game logic on the backend while abstracting away low-level details and providing useful utilities out of the box.

Writing Scripts

Scripts are a powerful way to extend the functionality of your game backend. They allow you to define custom endpoints that can be called by your game clients or other scripts. This document will cover the key aspects of writing scripts.


Request & Response

Every script has a defined request and response type. The request type specifies the structure of the data that the script expects to receive, while the response type defines the structure of the data that the script will return.

Rivet automatically validates the request data against the defined request type. If the request data does not match the expected structure, an error will be thrown before your script code is executed. This helps ensure that your script always receives valid input data.

Similarly, Rivet validates the response data returned by your script against the defined response type. If the response data does not match the expected structure, an error will be thrown. This helps catch bugs early and ensures that your script always returns valid data to the caller.

Here's an example of defining request and response types in a script:

export interface Request {
  version: string;
  regions?: string[];
  tags?: Record<string, string>;
  players: PlayerRequest[];
  noWait?: boolean;
}

export interface Response {
  lobby: LobbyResponse;
  players: PlayerResponseWithToken[];
}
modules/lobbies/scripts/find.ts

Public vs Internal Scripts

Rivet scripts can be marked as either public or internal in the module.json file. Public scripts are accessible to game clients and can be called directly via the generated SDK endpoints. Internal scripts, on the other hand, are only accessible within the backend and cannot be called directly by game clients.

Here's an example of defining public and internal scripts in module.json:

{
  "scripts": {
    "create": {
      "name": "Create Lobby",
      "description": "Creates a new lobby on-demand.",
      "public": true
    },
    
    "force_gc": {
      "name": "Force Garbage Collection",
      "description": "Rarely used. Forces the matchmaker to purge lobbies & players.",
      "public": false
    }
  }
}
modules/lobbies/module.json

Here is the updated section on auto-generated SDK endpoints, with more emphasis on the high-quality SDKs generated for all major game engines:


Auto-generated SDK Endpoints

One of the key benefits of Rivet is that it automatically generates high-quality SDK endpoints for your public scripts, tailored for all major game engines and platforms. These generated SDKs make it easy for game clients to interact with your custom backend functionality using idiomatic code in their language and environment of choice.

Rivet currently supports generating SDKs for the following platforms:

  • TypeScript (for web-based games)
  • GDScript (for Godot Engine)
  • C# (for Unity)

Here are some examples of how the generated SDK endpoints look for the findOrCreate script defined in find_or_create.ts:

import { Rivet } from "rivet-sdk";

const rivet = new Rivet();
const data = await rivet.lobbies.findOrCreate({
  // Request body  
});

The Rivet automatically updates your SDK whenever you make changes to your script's request/response schema. This ensures game clients always have access to the latest version of your backend API.


Auto-generated Documentation

Rivet automatically generates documentation for your scripts based on the request and response types, as well as any comments you provide in your script file. The generated documentation is hosted on rivet.gg and provides a clear reference for game developers who want to use your scripts.


The ScriptContext Object

The ScriptContext object is passed to every script function and provides access to various resources and utilities. Here are some of the key features of the ScriptContext:

  • Calling other modules: You can call scripts in other modules using ctx.modules.<module>.<script>(). This allows you to compose functionality across multiple modules.

  • Accessing the database: If your module has a database configured, you can access it using ctx.db. This allows you to perform database queries and mutations within your script.

  • Reading the user config: You can access the user-defined configuration for your module using ctx.config. This allows you to parameterize your script behavior based on configuration values.

  • Reading environment variables: You can access environment variables using ctx.environment. This allows you to configure your script behavior based on the runtime environment.

Here's an example of using the ScriptContext to call another module and access the database:

export async function run(
  ctx: ScriptContext,
  req: Request,
): Promise<Response> {
  await ctx.modules.rateLimit.throttlePublic({});

  const users = await ctx.db.query.users.findMany({
    where: Query.inArray(Database.users.id, req.userIds),
    orderBy: Query.desc(Database.users.username),
  });

  return { users };
}
modules/users/scripts/fetch.ts

Common Patterns

Here are some common patterns you'll encounter when writing module scripts:

Rate Limiting

You can use the rateLimit module to throttle requests to your script. This helps protect against abuse and ensures fair usage of resources. See the example in fetch.ts.

Token Authentication

You can use the users module to authenticate user tokens and retrieve the associated user ID. This allows you to implement user-specific functionality in your scripts. See the example in list_friends.ts.

Inferring Types from the Database

You can use the Database object to infer types for your database queries and mutations. This provides type safety and autocompletion for your database interactions. See the example in list_friends.ts.


Gotchas

Here are a couple of gotchas to keep in mind when writing module scripts:

Timeouts

By default, there is no timeout for script execution. If you need to enforce a timeout, you'll need to explicitly configure it.

Cancelled Requests

If a client cancels a request to your script, the script will still execute to completion. This is because module scripts are designed to be idempotent and not have side effects based on client behavior.


Calling Scripts via REST Requests

In addition to using the auto-generated SDK endpoints, you can also call your module scripts directly via raw REST requests. This can be useful for testing, debugging, or integrating with platforms that don't have a generated SDK available.

To call a script via REST, make a POST request to the following URL:

https://<your-backend-url>/modules/<module-name>/scripts/<script-name>/call
HTTP

The request body should be a JSON object containing the input parameters for your script. The response will be a JSON object containing the script's return value.

Here's an example of calling the findOrCreate script using cURL:

curl -X POST "https://localhost:6420/modules/lobbies/scripts/find_or_create/call" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.0.0",
    "players": [
      {
        "id": "player1"
      }
    ],
    "createConfig": {
      "region": "us-east-1",
      "maxPlayers": 4
    }
  }'
Command Line

The module backend automatically generates an OpenAPI specification file that describes all of your public script endpoints. You can find this file by running:

rivet backend generate-openapi --output <OUTPUT_PATH>
Command Line

Using Postman to Explore the OpenAPI Spec

Postman is a popular tool for testing and exploring REST APIs. You can use Postman to easily make requests to your Rivet script endpoints and inspect the responses.

To get started, import the generated OpenAPI schema file into Postman:

  1. Open Postman and click the "Import" button
  2. Select the OpenAPI schema file from your project directory
  3. Click "Import"

This will create a new Postman Collection with pre-configured requests for all of your public script endpoints. You can then use Postman's interface to modify the request parameters, send requests, and view the responses.

Some alternatives to Postman include:

Using tools like Postman in combination with the OpenAPI spec provides a great way to explore, test, and debug your Rivet scripts without needing to write any client code. It's especially useful during development to quickly verify that your scripts are working as expected.

Once you've finalized your script design, you can then use the auto-generated SDK endpoints to integrate them into your game clients for production use. The OpenAPI spec acts as the source of truth that keeps your backend and client in sync.