Skip to main content

Documentation Index

Fetch the complete documentation index at: https://v5.rpgjs.dev/llms.txt

Use this file to discover all available pages before exploring further.

Structure

The starter separates client boot, server boot, shared client config, and modules.

client.ts

client.ts starts the browser client in MMORPG mode:
import { startGame, provideMmorpg } from "@rpgjs/client";
import configClient from "./config/config.client";
import { mergeConfig } from "@signe/di";

startGame(
  mergeConfig(configClient, {
    providers: [provideMmorpg({})],
  }) 
);
Use this entry when the client connects to a remote RPGJS server. By default, MMORPG mode stores a stable session id in localStorage and passes it to @signe/room as the connection session id. Refreshes and multiple tabs from the same browser therefore restore the same player session while each WebSocket keeps its own connection id. Use connectionIdScope: "session" to keep the session only for one browser tab, or connectionIdScope: "ephemeral" to create a new player session on each page load:
providers: [provideMmorpg({ connectionIdScope: "session" })]
If your server uses engine.auth(), send the token with the MMORPG connection query. RPGJS includes this query on the initial lobby connection and on map-room reconnects:
providers: [
  provideMmorpg({
    query: () => ({
      token: localStorage.getItem("token")
    })
  })
]

server.ts

server.ts creates the game server and registers your providers:
import { createServer, provideServerModules, LocalStorageSaveStorageStrategy } from "@rpgjs/server";
import { provideMain } from "./modules/main";
import { provideSaveStorage } from "@rpgjs/server";
import { provideTiledMap } from "@rpgjs/tiledmap/server";

export default createServer({
  providers: [
    provideMain(),
    provideSaveStorage(new LocalStorageSaveStorageStrategy({ key: "save" })),
    provideServerModules([]),
    provideTiledMap()
  ]
});
This is where you plug modules, maps, database content, save strategies, or server adapters.

standalone.ts

standalone.ts runs the client and server together for a standalone RPG:
import { mergeConfig } from "@signe/di";
import { provideRpg, startGame } from "@rpgjs/client";
import startServer from "./server";
import configClient from "./config/config.client";

startGame(
  mergeConfig(configClient, {
    providers: [provideRpg(startServer)],
  })
);
Use this entry when you want a single-player RPG running entirely from the client app.

config/config.client.ts

config.client.ts contains the common client setup shared by MMORPG and standalone RPG:
import { provideClientGlobalConfig, provideClientModules, Presets } from "@rpgjs/client";
import { provideMain } from "../modules/main";
import { provideTiledMap } from "@rpgjs/tiledmap/client";

export default {
  providers: [
    provideTiledMap({
      basePath: "map",
    }),
    provideClientGlobalConfig(),
    provideMain(),
    provideClientModules([
      {
        spritesheets: [
          {
            id: "hero",
            image: "spritesheets/hero.png",
            ...Presets.RMSpritesheet(3, 4)
          }
        ]
      }
    ])
  ],
};
Put here everything the client always needs: map loading, global config, modules, spritesheets, sounds, and resolvers.

provideClientGlobalConfig()

provideClientGlobalConfig() is the client-side place for shared global configuration. It can hold built-in options such as keyboardControls, and also any custom object you want to expose everywhere in the client through dependency injection.
import { provideClientGlobalConfig } from "@rpgjs/client";

export default {
  providers: [
    provideClientGlobalConfig({
      keyboardControls: {
        up: "z",
        down: "s",
        left: "q",
        right: "d",
        action: "enter",
        escape: "escape"
      },
      ui: {
        locale: "fr",
        showDamagePreview: true
      },
      api: {
        baseUrl: "/api"
      }
    })
  ]
};
If you omit keyboardControls, RPGJS injects the default bindings automatically:
{
  up: "up",
  down: "down",
  left: "left",
  right: "right",
  action: "space",
  escape: "escape"
}
The action binding also accepts an object when the action key should send a custom action input. This keeps the key generic in built-in components while letting the game decide which action and payload to send.
provideClientGlobalConfig({
  keyboardControls: {
    up: "z",
    down: "s",
    left: "q",
    right: "d",
    action: {
      bind: "space",
      action: "projectile:shoot",
      data: (client, sprite) => ({
        target: client.pointer.world(),
        source: "keyboard",
        playerId: sprite.id
      })
    },
    escape: "escape"
  }
})

Custom action inputs

Custom action inputs use the same client-to-server flow whether they come from the configured action key or from code. The client sends a payload shaped like { action, data }, and the server receives that payload in the player’s onInput() hook.
// Client code
client.processAction("projectile:shoot", {
  target: client.pointer.world(),
  source: "mouse"
})
// Server player hook
export const player = {
  onInput(player, input) {
    if (input.action !== "projectile:shoot") return

    const target = input.data?.target
    // Validate all client-provided data before using it.
  }
}
Only the default "action" input, or Control.Action, automatically triggers nearby event onAction() hooks. A custom action such as "projectile:shoot" goes directly to player.onInput() and does not trigger event interactions by itself. Do not confuse this player hook with map movement input processing. Custom actions are sent with client.processAction() and handled in player.onInput(); movement inputs are queued separately by the map and processed by the movement loop. You can retrieve this global config anywhere on the client with inject(GlobalConfigToken):
import { inject } from "@signe/di";
import { GlobalConfigToken } from "@rpgjs/client";
import type { KeyboardActionConfig } from "@rpgjs/client";

const config = inject(GlobalConfigToken) as {
  keyboardControls: {
    up: string;
    down: string;
    left: string;
    right: string;
    action: string | KeyboardActionConfig;
    escape: string;
  };
  ui?: {
    locale?: string;
    showDamagePreview?: boolean;
  };
};

console.log(config.keyboardControls.action);
console.log(config.ui?.locale);
This is useful in client services, GUI components, or custom systems that need access to global input bindings or project-specific client configuration.

modules/main

The main module is usually where you declare your first server hooks and maps:
import { createModule } from "@rpgjs/common";
import server from "./server";

export function provideMain() {
  return createModule("main", [{
    server
  }]);
}
This keeps your game logic modular. You can add more modules later for battle, UI, quests, chat, or any custom feature.