Ren'Py supports web builds through its Emscripten-based HTML5 export. The output is a folder of HTML, JS, WASM, and game data that Wavedash hosts directly. Wavedash detects Ren'Py automatically and drives the loading overlay from the engine's boot phases; calling the SDK from inside your script is done through renpy.emscripten.run_script(...).
Prerequisites
- Ren'Py SDK (8.x)
- The Ren'Py web platform package installed into the SDK (download from the same page; it unpacks a
web/folder into the SDK root)
Build for web
From the Ren'Py launcher: select your project → Build Distributions → check Web → build to a destination folder.
From the command line:
"$RENPY_SDK/renpy.sh" launcher web_build ./my-project --destination ./build
The output contains index.html, renpy.wasm, renpy.js, game.zip, and icons. Point upload_dir at that folder.
Loading progress
You don't have to wire this up — Wavedash detects ?engine=RENPY from the iframe URL and installs a progress shim that watches Ren'Py's own boot phases (engine download, game data download, unpacking, script load). The Wavedash loading overlay tracks them automatically.
Initialize the SDK
Calling Wavedash.init() is required. It opts your game into Wavedash platform features and dismisses the loading overlay. Call it from before_main_menu, which is the earliest hook where the game is interactive.
game/00_wavedash.rpy
default wavedash_inited = False
init -100 python:
def wavedash_init_once():
if store.wavedash_inited:
return
store.wavedash_inited = True
if not renpy.emscripten:
return
renpy.emscripten.run_script("""
if (window.Wavedash) {
window.Wavedash.init({ debug: true });
}
""")
label before_main_menu:
$ wavedash_init_once()
return
renpy.emscripten is the documented Ren'Py module for web JS interop. On desktop builds it's None, so the truthy check doubles as a platform guard.
Calling SDK methods
Inline more JS in the same run_script block (or write a helper for each beat). Unlocking an achievement:
$ renpy.emscripten.run_script("""
if (window.Wavedash) {
window.Wavedash.setAchievement('first_chapter_complete', true);
}
""")
Reading the current player's username (sync round-trip via run_script_string):
$ raw = renpy.emscripten.run_script_string("JSON.stringify(window.Wavedash?.getUsername() ?? null)")
$ import json
$ username = json.loads(raw) or "Player"
Any other JS-SDK method (setStat, toggleFullscreen, toggleOverlay, getUserAvatarUrl, etc.) is reachable the same way — they're all on window.Wavedash. See the JS SDK reference for the full list.
Fullscreen
Ren'Py's built-in Preference("display", "fullscreen") doesn't work on Wavedash web — the iframe doesn't own the real fullscreen target. Use the Wavedash helper instead, from a preferences screen or button:
screen wavedash_fullscreen_button():
textbutton "Fullscreen":
action Function(renpy.emscripten.run_script, "window.Wavedash?.toggleFullscreen()")
wavedash.toml
game_id = "YOUR_GAME_ID_HERE"
upload_dir = "./build"
[renpy]
version = "8.6.0"
executable = "game.zip"
The [renpy] section tells the Wavedash CLI to treat the build as a Ren'Py 8.x web export.
Notes
main_menulabel vs screen: Ren'Py checks for amain_menulabel before using themain_menuscreen. If you only define a screen, add an explicitlabel main_menu: call screen main_menuso Ren'Py actually mounts it.- Template screens required: Real Ren'Py games include
game/screens.rpy,game/gui.rpy, andgame/gui/images from the SDK template. If you started from a barescript.rpy, the ESC menu (Save / Load / Preferences) will be missing. - Web build size: Ren'Py web builds can be 50MB+ with audio/image assets. Compress audio to OGG and resize images for web before building.