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 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.