Structure
The starter separates client boot, server boot, shared client config, and modules.
client.ts
client.ts starts the browser client in MMORPG mode:
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 the RPGJS room adapter 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:
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:
server.ts
server.ts creates the game server and registers your providers:
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:
Use this entry when you want a single-player RPG running entirely from the client app.
Because standalone mode runs the server inside the browser, its production
bundle includes the code imported by server.ts. Do not put secrets or private
service credentials in a standalone game bundle.
config/config.client.ts
config.client.ts contains the common client setup shared by MMORPG and standalone RPG:
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.
If you omit keyboardControls, RPGJS injects the default bindings automatically:
Partial keyboardControls objects are merged with these defaults, so a game can
override one key without redefining every movement/action binding.
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.
The dash binding is a movement input. In MMORPG mode the client predicts it
locally through the same movement queue as directional inputs, while the server
validates and acknowledges the dash position.
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.
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):
This is useful in client services, GUI components, or custom systems that need access to
global input bindings or project-specific client configuration.
modules/server.ts and modules/client.ts
The main module keeps client and server ownership explicit. Define server hooks
in modules/server.ts:
Define visual behavior in modules/client.ts, then install each definition with
the matching runtime provider as shown above. This keeps server code out of the
client bundle and client components out of the server bundle when building an
MMORPG. This isolation depends on the import graph: never import the server
module from client code. See Creating Modules for the
complete bundle-ownership rules and the standalone exception.