RunXR

Publish a game on RunXR

RunXR is a controller-first web game portal. Any HTML5 game that runs in a browser can be listed — whether it lives on RunXR or stays hosted on your own site. This guide covers both paths.

Overview

A game on RunXR is one catalog entry with a title, a category, artwork, and a URL. That URL is loaded in a full-screen frame when a player launches the game. There are two ways to provide it:

  • Host your own game — the URL is a full https:// address on your own domain (like Poki). You keep control of the code and updates.
  • Upload an HTML game — the game is served by RunXR from a path such as /games/your-game.html.

Anyone can publish. Sign in, open the account menu (Select on a controller) and choose Activate dev mode. That opens the Dev Studio, where you add your game: title, category, URL, description, controls and cover art. New games are saved as drafts and go live once a moderator reviews and publishes them; you can keep editing your own games at any time. Once activated, the account menu links straight to your Dev Studio.

Quick start

  1. Get your game playable at a public HTTPS URL, or export it as a single folder of static files.
  2. Make sure it can be embedded in a frame (see Host your own game).
  3. Optionally add the RunXR SDK so the game reports scores and exits cleanly.
  4. Sign in, choose Activate dev mode from the account menu, then in the Dev Studio click Add game and fill in the title, description, category, age rating, device compatibility, features, game URL, thumbnail, cover and (optionally) background image.
  5. Save. A moderator reviews and publishes it, and the game appears at runxr.app/your-slug.

Host your own game

Set the game URL to your full address, for example https://vampirexr.vercel.app/. RunXR loads it in an iframe and grants it the permissions a game needs:

gamepad, fullscreen, autoplay,
accelerometer, gyroscope, xr-spatial-tracking

For the frame to load, your host must allow embedding. Two headers control this:

  • Do not send X-Frame-Options: DENY or SAMEORIGIN.
  • If you send a Content-Security-Policy, its frame-ancestors must include the RunXR origin:
Content-Security-Policy: frame-ancestors https://runxr.app

Most static hosts (Vercel, Netlify, Cloudflare Pages, itch.io HTML uploads, GitHub Pages) allow framing by default, so there is usually nothing to change. Serve the game over HTTPS.

Test it yourself: if <iframe src="YOUR_URL"> shows a blank frame with a “refused to connect” console error, your host is blocking embedding — fix the headers above.

Upload an HTML game

A self-contained HTML5 game can be served directly by RunXR. Drop the files under the app’s games/ folder (they are served at /games/… with the headers the player needs) and point the catalog URL at the entry file, e.g. /games/your-game.html. Read the controller with the small helper library that ships with the platform:

<script src="/games/lib/pad.js"></script>
<script>
  function frame() {
    Pad.poll();                     // call once per frame
    if (Pad.held('LEFT'))  player.x -= 4;
    if (Pad.held('RIGHT')) player.x += 4;
    if (Pad.pressed('A'))  shoot();  // pressed = this frame only
    const aim = Pad.axis(0);         // left stick X, -1..1
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
</script>

On phones and tablets pad.js also draws an on-screen controller (a thumb-stick, A, B, X, Pause and Menu) that feeds the same Pad state, so a game written for a gamepad is playable by touch with no extra work.

Pad methods: poll(), held(button), pressed(button), axis(0|1), dir() / vdir() (−1/0/1), menu() (open the RunXR Quick Menu), exit() (quit to RunXR) and sysPaused() (true while the Quick Menu is open — GameKit.loop freezes automatically). Buttons are named A B X Y LB RB LT RT START SELECT UP DOWN LEFT RIGHT HOME. The keyboard is mapped automatically: arrows / WASD → D-pad, Space / Enter / Z → A, X → B, C → X, V → Y, Q / E → LB / RB, P → Start, Shift → Select, Esc → Quick Menu.

pad.js also exposes GameKit with canvas helpers used by the bundled games: setup(w, h) (creates a letterboxed canvas with id c), loop({ update, draw, w, h, ctx }) (fixed-step loop with Start-to-pause built in), text(), overlay(), best(key) / setBest(key, v), beep() and rand(a, b).

The RunXR SDK

The SDK is optional and works for any game, hosted anywhere. It is inert when the game runs on its own, so it is safe to ship in your game permanently. Include it once:

<script src="https://runxr.app/sdk/runxr.js"></script>

Then call these from your game:

RunXR.ready();        // hide the launcher's "how to exit" hint once loaded
RunXR.score(1234);    // report a score; RunXR keeps each player's best
RunXR.menu();         // open the RunXR Quick Menu (e.g. from your own pause screen)
RunXR.exit();         // quit straight back to the RunXR menu (your "Quit" button)
RunXR.onPause(fn);    // the Quick Menu opened: freeze the game, mute audio
RunXR.onResume(fn);   // the Quick Menu closed: carry on
RunXR.paused;         // true while the Quick Menu is open
RunXR.embedded;       // true only when running inside RunXR

The SDK also wires Esc, Start + Select and the Guide button to menu() for you, and announces itself with a gs-hello message on load. window.GameStation is a legacy alias of window.RunXR.

The Quick Menu. Like a console’s home overlay, RunXR opens a Quick Menu over any running game when the player presses Guide, Start + Select, Esc, or taps the Menu button in the corner. It offers Resume, Restart game, Fullscreen, Favorite and Quit game, and shows the session time and best score. The game keeps running behind it unless it listens for the pause message, so wire RunXR.onPause() / onResume() (or use GameKit.loop, which freezes automatically). Without the SDK the Quick Menu still works; the SDK lets your UI trigger it, pause correctly and report scores.

Build a game from scratch

The fastest path is the starter file at https://runxr.app/sdk/template.html: one self-contained HTML page (no build step, no dependencies) that already does everything RunXR expects. Copy it, replace update() and draw(), host it anywhere over HTTPS, and list it. It shows the five things every RunXR game needs:

  1. Include the SDK — <script src="https://runxr.app/sdk/runxr.js"></script>. It is inert outside RunXR, so the same file runs on your own site.
  2. Read a gamepad and the keyboard with the RunXR button names (A B X Y LB RB LT RT SELECT START UP DOWN LEFT RIGHT), polling navigator.getGamepads() once per frame and tracking “held” vs “pressed this frame”.
  3. Render to a fixed logical resolution (e.g. 960×540) and letterbox it to the window — the RunXR frame is full-screen at any aspect ratio, so never assume a size.
  4. Pause on Start, and leave Guide, Start + Select and Esc alone: RunXR uses them to open its Quick Menu. Freeze the game in RunXR.onPause().
  5. Call RunXR.ready() once the first frame is drawn and RunXR.score(n) whenever the score changes.

Design rules that make a game feel native on RunXR: it must be fully playable with only a controller (no mouse-only menus, no text input); the first screen should start on A, not on a click; sound should start on the first button press (browsers block autoplay until a gesture); keep the UI readable from a couch (large type, high contrast, no hover-only affordances); and save progress in localStorage because players may come back later.

Any engine that exports to HTML5 works the same way — Phaser, PixiJS, Three.js, Godot, Unity WebGL, Construct, GDevelop, PlayCanvas, Babylon.js — as long as the exported page follows the rules above and can be embedded (headers).

Make an existing game compatible

Already have a browser game? Work through this list; most games need only the first three items.

  1. Embeddable. Serve it over HTTPS and make sure the host does not send X-Frame-Options or a Content-Security-Policy whose frame-ancestors excludes https://runxr.app. Test with a local page containing <iframe src="YOUR_URL">.
  2. Frame-safe. Never navigate the top window (window.top.location, target="_top" links); do not rely on document.fullscreenElement being your own document (RunXR already runs full-screen and grants the fullscreen permission); and use window.innerWidth/innerHeight plus a resize listener instead of a hard-coded viewport.
  3. Controller. Add gamepad support if it only has keyboard or mouse input — the template’s Pad block is ~25 lines you can paste in. Map: stick / D-pad = move, A = confirm / primary, B = back, Start = pause. Every menu, including “press any key” and game-over screens, must be reachable with the pad. Mark Gamepad Controls in the listing once done.
  4. Reserved input. Unbind anything on Esc and the Guide button, and never require Start + Select together — RunXR uses all three for its Quick Menu.
  5. SDK. Add the script tag and call RunXR.ready() after load, RunXR.score(n) on score changes, RunXR.exit() from your own “Quit” button, and pause in RunXR.onPause() / resume in RunXR.onResume() so the game freezes behind the Quick Menu. Optional but recommended; scores show on the game’s detail card.
  6. Touch. If you want the Mobile Phone / Tablet devices ticked, provide on-screen controls (RunXR’s pad.js overlay does this for uploaded games; hosted games ship their own) and set touch-action: none on the canvas so swipes do not scroll.
  7. Audio & autoplay. Create or resume the AudioContext on the first button press; the frame allows autoplay but browsers still need a gesture.
  8. Assets. Use relative URLs or absolute HTTPS URLs; load fonts/textures from hosts that send CORS headers. Mixed content (http) is blocked inside the HTTPS frame.
  9. Listing. Prepare a 16:9 thumbnail, a 3:4 cover, a description, an age rating, device compatibility and features, then add the game in the Dev Studio.

Message protocol

RunXR and the game talk with window.postMessage. The SDK and pad.js send these for you, but any game can post them directly to window.parent (target origin * is fine; RunXR only accepts messages from the active game frame).

Message (game → RunXR)Effect
{ type: 'gs-hello' }Sent by the SDK on load. Informational.
{ type: 'gs-ready' }Hides the “how to exit” toast.
{ type: 'gs-score', score: 1234 }Records the score; RunXR keeps each signed-in player’s best per game.
{ type: 'gs-menu' } or { type: 'gp-menu' }Opens the RunXR Quick Menu over the game (same as Guide or Start + Select).
{ type: 'gs-exit' } or { type: 'gp-exit' }Quits the game straight back to the RunXR menu.
Message (RunXR → game)Meaning
{ type: 'gs-pause' }The Quick Menu opened. Freeze gameplay and timers, mute or duck audio. The SDK calls your RunXR.onPause() handlers.
{ type: 'gs-resume' }The Quick Menu closed (Resume). Carry on. The SDK calls your RunXR.onResume() handlers.
{ type: 'gp-key', key: 'ArrowLeft', down: true }Keyboard events forwarded from the launcher to same-origin (uploaded) games so focus never gets lost. Hosted games do not receive these; they read the keyboard themselves.

Controller & input

RunXR is built for gamepads, and every game should be fully playable with one. The keyboard mirrors the controller so a game works without a pad too. Recommended mapping:

ControllerKeyboardTypical use
D-pad / Left stickArrow keys / WASDMove
AEnter / SpacePrimary action / confirm
BEscCancel (Esc also opens the RunXR Quick Menu)
StartPPause
Guide / Start + SelectEscRunXR Quick Menu (reserved)

Keep Guide and Start + Select free — RunXR uses them to open its Quick Menu. RunXR adapts its own on-screen prompts to Xbox, PlayStation and Nintendo controllers and can swap the confirm button to the right (Nintendo style) in Settings; your game should read the standard Gamepad mapping (button 0 = bottom face button) and label prompts generically or by position. Fill each game’s “Controls” field (one line per control) so players see them on the detail card before launching.

Listing fields & artwork

When you add a game in the Dev Studio you provide:

  • Title, Description and Category — the category is the shelf your game appears on.
  • Search title (optional, max 60 characters) — what Google shows instead of your title. Use the words people actually search for, e.g. “Brick Blaster - Free Brick Breaker Game Online”. RunXR adds “| RunXR”.
  • Age rating — EC (Early Childhood), E (Everyone), E10 (Everyone 10+), T (Teen) or M (Mature 17+). Shown as a badge on the detail card and the home hero.
  • Device compatibility — tick where the game plays well: VR, Mobile Phone, Retro Emulator, TV, Tablet, Computer.
  • Features — tick what applies: Gamepad Controls, VR, Multiplayer, Local Co-op, Screen Controls. Features and devices are shown as chips on the game’s detail card.
  • Game URL — the full https:// address where the game is playable (see Host your own game).
  • Thumbnail URL — a landscape image (16:9) used on tiles and shelves.
  • Cover URL — portrait box art (3:4) used by the Shelf, Case, Carousel and Coverflow views.
  • Background URL (optional) — a wide image for the home-page hero banner; falls back to the cover.

Players can rate every game 1–5 stars and leave a comment (one review per signed-in player; they can update it later). The average and count appear on the detail card and the home hero, so complete listings with good artwork tend to gather reviews faster.

Host images anywhere that serves them over HTTPS. Games without artwork get a generated tile. The Verified badge (a green check on the game) is granted by RunXR admins to games they have reviewed; it cannot be self-assigned.

Sharing & install

Every published game has a shareable link at runxr.app/your-slug that opens straight to the game. RunXR is also an installable app (PWA): players can add it to a phone home screen or install it on desktop from the account menu, and it opens in its own window.

Launch checklist

  • Game runs over HTTPS and loads inside an iframe (no frame-blocking headers).
  • Fully playable with a gamepad; keyboard fallback works.
  • Guide, Start + Select and Esc are not used by the game, and it pauses on RunXR.onPause().
  • (Recommended) RunXR SDK included, with ready() and score() wired.
  • Title, description, category, age rating, device compatibility and features ready.
  • Thumbnail (16:9), cover (3:4) and optionally a wide background image, hosted over HTTPS.

Have all of that? Activate dev mode and add it in the Dev Studio, or send it to us and we will publish it. Questions: hello@runxr.app.

For AI agents

Building or porting a game with an AI coding agent (Claude Code, Cursor, Copilot, ChatGPT…)? Point it at the plain-text spec at https://runxr.app/llms.txt — it contains this whole guide condensed into rules, the input mapping, the message protocol, the listing fields and the starter template, with no HTML to wade through. A prompt that works well:

Read https://runxr.app/llms.txt and https://runxr.app/sdk/template.html.
Then build me a [genre] game as a single HTML file that follows every RunXR rule
(gamepad + keyboard input, Guide/Start+Select/Esc reserved for the Quick Menu, pause on RunXR.onPause, fixed logical resolution
letterboxed to the window, RunXR.ready() and RunXR.score() wired, embeddable).

For an existing game: “Read https://runxr.app/llms.txt and make this game RunXR-compatible, following the ‘Make an existing game compatible’ checklist.” The spec is generated from the same source as this page and is kept in sync automatically.