# Putting an exported game in a web page
Take a game made with the Game Kits out of GripForge and serve it yourself: what `gripforge game-export` writes,
how to serve it, what the page accepts, and what it says when it cannot start. Readable offline: `grip-forge docs
web-export`, or the MCP tool `gripforge_docs` (topic `web-export`).
## What it is for
A web export is a folder of static files: the page, the player, the logic of the game's kits and its assets. It
plays without GripForge's servers and without a login. You host it as any static site.
## Prerequisites
- The GripForge CLI, logged in, with access to the workspace that owns the project (also for a public game).
- Node.js 22.19 or later, for the CLI and for the local launcher of the export.
- A Game Kit project id (`gkp_…`).
## Install and first run
```sh
gripforge game-export gkp_... ./my-game
cd my-game
npm run dev # Game ready: http://127.0.0.1:5173
npm run dev -- --port 5174 # another port
```
No dependency is installed: `npm run dev` runs `server.mjs`, a small static server that ships in the folder.
## What the folder holds
| Path | What |
|---|---|
| `index.html` | The page: one `
` and the player's module. |
| `game.json` | The project: its configuration and content. This is the file you edit. |
| `player/` | The compiled page player (`player.js`, `player.css`, its workers) and the web views of the game's kits only. |
| `runtime-rust/` | When the game is drawn by the GripForge player: the player (WebGPU and WebGL2 builds), the presentation modules of the game's kits, and `kit-modules/` — the logic of the game's kits, one WebAssembly module per kit plus the core. |
| `assets/` | The models, textures and sounds of the game. |
| `third-party-kits/` | The kits of your own the game declares ([Writing a kit](../kit-third-party.md)). |
| `server.mjs`, `package.json`, `README.md` | The local launcher (`npm run dev`). |
| `.gripforge/web.lock.json` | The kit versions and the hash of every delivered file. |
## Serving it from your own site
The page of the export is complete: publish the folder as it is, under any path. `index.html` finds every file
relative to its own address, so nothing has to be at the root of the site.
What your server must do:
| Requirement | Why |
|---|---|
| Answer `.wasm` as `application/wasm` | The browser refuses to compile WebAssembly served as anything else. |
| Answer `.js` and `.mjs` as `text/javascript` | Module scripts are refused with another type. |
| Serve every file of the folder, with the same relative paths | The page loads `./game.json`, `./player/…`, `./runtime-rust/…`, `./assets/…`. |
`server.mjs` does exactly this (GET and HEAD only, `cache-control: no-store`, a 404 for anything outside the
folder): read it as the reference.
## Page options
Added to the address of the page:
| Option | Values | Effect |
|---|---|---|
| `?logic=` | `wasm` (default), `kits` (the same), `ts` | Which logic runs: the Rust logic of the kits (one module per kit), or the TypeScript kit host. Without it, `logic` of `game.json` decides, then `wasm`. A game with a third-party kit does not play it under `ts`: the page says so in a red line. |
| `?renderer=` | `bevy`, `three` | Draws with the other renderer, to compare or diagnose. The page shows no control for it. |
## Updating an export
```sh
gripforge game-export gkp_... ./my-game --check # what would change, nothing written
gripforge game-export gkp_... ./my-game # apply
gripforge game-export gkp_... ./my-game --force # back up local edits of managed files, then replace them
```
Nothing updates silently. Files you added are never touched. If a delivered file was edited locally, the whole
update stops and lists the conflicts; `--force` backs them up in `.gripforge/web-backups/` and replaces them.
`--json` prints the report (`added`, `updated`, `removed`, `preserved`, `conflicts`, `budget`).
After an export the CLI checks the models of `assets/` against the render budget and prints warnings; a warning
never refuses an export (`grip-forge docs render-budget`, or `gripforge game-budget`).
## Errors
### From the CLI
| Message | Cause |
|---|---|
| `Expected a Game Kit project id (gkp_…).` | The first argument is not a project id. |
| `Usage: gripforge game-export
[folder] [--check] [--force] [--json]` | An unknown option or a missing argument. |
| `Incomplete export: ` | The server's export lacks a required file (`index.html`, `game.json`, `package.json`, `server.mjs`, `player/player.js`). |
| `Integrity check failed: ` | A downloaded file is not the one the export names (size or SHA-256). Nothing is installed. |
| `Export download failed for (HTTP ).` | A file could not be downloaded. Nothing is installed. |
| `This directory belongs to a different export. Choose another directory.` | The folder holds the export of another project or of another server. |
| `Another export is active. …` | `.gripforge/export.lock` exists: another export is running, or one was interrupted. |
| `File changed during export: ` | A file was edited while the update was being applied. |
Files are installed only after every download succeeded.
### From the page
The page never starts a game it could only play wrong: it stops and says why.
| What the page says | Cause |
|---|---|
| `Unable to start the game. Cannot load game.json ()` | `game.json` is not served next to `index.html`. |
| `The game player is missing on this server. …` | The files of `runtime-rust/` are not served, or not all of them. |
| `This browser cannot draw the game: neither WebGPU nor WebGL2 answers. …` | No graphics acceleration in this browser. |
| `This game page and the installed player are not of matching versions. …` | The page and the player do not speak the same protocol version: export the game again. |
| `The game player could not start. …` | The player was there and the browser could draw, and it still failed: reload, then look at the console. |
| `kit-modules/kit-modules.json: the manifest of the kit modules was not read (HTTP 404)` | `runtime-rust/kit-modules/` is not served. |
| `@: no module in the kits package` | The kit modules served do not hold a kit of the game: export again. |
| `: … its module has sha256 …, the game declares …` | The module served is not the one the game declares: export again. |
| `: the kit module is version , the game declares ` | `kit-modules.json` and `game.json` come from two different exports. |
The player draws with WebGPU and falls back to WebGL2 by itself; it never falls back to another renderer in
silence. The console line `[gripforge] kit logic modules: the core and kits` tells which logic modules were
loaded.
## Known limits
- The export does not include GripForge's backend or generation services.
- Kits that need online services (`net.*`, `fps.network`) still need them; the CLI lists them after the export
(`Online services required by: …`). An export with `fps.network` ships `fps-server.mjs` (`npm run server`) for
private rooms.
- Exporting does not publish the project or change its visibility.
- Legacy kits and unpublished source overlays are refused by the export.
- An update from the site knows GripForge's kits only: after it, run `gripforge kit verify .` on a game with a kit
of your own, and add the kit again if it no longer verifies.
## Versions
The export manifest is `gripforge.web-export/1`; the lock of the folder is `gripforge.web-lock/1`. The kit versions
delivered are in `.gripforge/web.lock.json`. `gripforge game-export` exists since CLI 0.3.15.