Sparkade/docs
FAQOpen app
Developer documentation

Build for Sparkade

Sparkade games are built with Phaser, a powerful open-source HTML5 game framework. This page is your complete reference for how to build, submit, and manage your game on Sparkade.

Overview

Sparkade is a mobile-first platform with a TikTok-style scrollable game feed. Players swipe through games and tap to play instantly. Any user can submit a game through the in-app flow.

The platform provides leaderboards, cloud saves, in-app purchases, and multiplayer support at no cost. You focus on building the game.

Under the hood, every game runs inside a sandboxed iframe. Before your game script executes, Sparkade injects a set of functions onto the window object — this is the Sparkade OS bridge. Through this bridge your game communicates with the platform: signalling when gameplay starts, submitting scores, reading and writing save data, triggering purchases, and identifying the current player. You never need to make API calls or handle authentication. The bridge handles all of that. Your game just calls the functions.

Quick start

1
Open Sparkade on your phone and create an account.
2
Tap the + button in the bottom nav to open the submission flow.
3
Build your game — using the AI method below, or write it yourself using this reference.
4
Upload your single game.js file (max 2 MB).
5
Your game is checked instantly by the automated analyser. If it passes, it goes live in the feed immediately.

Building with AI

We have built a skill system that teaches any AI coding tool how to produce correctly structured Sparkade games. Share this URL with Claude, ChatGPT, Cursor, or any AI assistant:

https://raw.githubusercontent.com/sparkade-labs/sparkade-skills/main/INDEX.md

Describe your game idea. The AI fetches the relevant skills, builds the complete game file, and hands you a ready-to-upload game.js.

You do not need to know Phaser or JavaScript. Describe your idea in plain English and the AI handles the implementation.

File format

The constraint of a single file is intentional. It means your game is self-contained — nothing to host, no CDN to configure, no build pipeline to maintain. You hand Sparkade one file and the platform takes care of everything else: serving it, sandboxing it, loading Phaser before it runs, and injecting the bridge layer.

The trade-off is that external resources are not available for single-player games. Images and audio need to be embedded directly in the file as base64 strings, or drawn programmatically using Phaser's graphics API. In practice this is less limiting than it sounds — Phaser's procedural drawing tools are expressive, and base64-encoded sprites compress well.

Maximum file size: 2 MB
Single-player games: no external network requests — all assets must be embedded
Multiplayer games: wss:// WebSocket connections to your game server are supported
All images and audio embedded as base64 or drawn procedurally with Phaser Graphics
Phaser 3.90.0 is loaded by the platform automatically — do not import it yourself
Single-player games that attempt external network requests are rejected automatically during validation. WebSocket connections are only available for multiplayer games connecting to a dedicated game server.

Scene setup

Phaser organises game logic into scenes — discrete states that can be started, stopped, paused, and layered. Every Sparkade game uses exactly four scenes in a fixed sequence. This structure is not arbitrary: it ensures the platform can reliably detect when a game has started, when it ends, and that there is always a clear exit path for the player.

The four scenes each have a specific responsibility. BootScene exists purely to generate textures — Sparkade games draw all their graphics procedurally, and this is where that happens. MenuScene is the first thing the player sees, and where gameStarted() is called. GameScene contains all gameplay. GameOverScene shows the result and offers Play Again and Exit actions.

scene: [BootScene, MenuScene, GameScene, GameOverScene]

BootScene — generates all procedural textures. No gameplay. Transitions immediately to MenuScene.

MenuScene — game title, brief instructions, start button. Calls window.gameStarted() when play begins.

GameScene — all gameplay. Calls window.gameOver(score) when the run ends.

GameOverScene — run summary, score, play again and exit actions.

Canvas & config

Sparkade games render at 390×844 — the logical dimensions of a modern iPhone in portrait. This is the canvas your game is designed against. On actual devices the canvas scales to fit using Phaser's FIT mode, so it will display correctly at any screen size without you needing to think about it.

The resolution setting is important: without it, games render at logical pixel density and appear blurry on high-DPI phones. Setting it to window.devicePixelRatio ensures native sharpness on all modern devices. Copy this config exactly — these values have been tested across the device range Sparkade runs on.

const W = 390, H = 844;

const config = {
  type:            Phaser.AUTO,
  width:           W,
  height:          H,
  backgroundColor: '#080808',
  parent:          'game-container',
  resolution:      window.devicePixelRatio || 1,
  scale: {
    mode:       Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
  },
  physics: {
    default: 'arcade',
    arcade:  { gravity: { y: 0 }, debug: false },
  },
  scene: [BootScene, MenuScene, GameScene, GameOverScene],
};

new Phaser.Game(config);

Required bridge calls

The Sparkade OS bridge is a set of functions injected onto window before your game loads. Two of them are mandatory for every game — they are how the platform knows your game is running and how the player gets back to the feed.

window.gameStarted() tells the platform that the player is actively playing. Call it when gameplay actually begins — not when the menu loads. The platform uses this signal to start the session timer that determines Sparks rewards.

window.gameExit() hands control back to the Sparkade feed. It must be reachable from inside the game at all times — from the pause menu, the game over screen, or the main menu. A player should never be stuck inside a game with no way out.

window.gameStarted()   // call once when gameplay begins — not on menu load
window.gameExit()      // call only from a deliberate exit button — never on death
The exit button must be accessible from inside the game — from the pause menu, game over screen, or main menu. It must never be permanently overlaid on active gameplay.

Scoring

Scoring is opt-in. Not every game needs a leaderboard — cozy games, narrative games, and sandbox games work perfectly well without one. If your game does have a score, calling gameOver(score) with a positive integer is all you need to do. Sparkade handles the rest: validating the score, updating the leaderboard, awarding Sparks, and determining personal bests.

Leaderboards are created automatically the first time any score is submitted for a game. Two leaderboards appear on the game card — global (all players) and friends (players the current user follows). You do not configure any of this. The platform registers onScoreResult and fires it back to your game with the result, so you can show the player their rank, Sparks earned, and a personal best celebration if applicable.

window.gameOver(score);    // score is a non-negative integer

window.onScoreResult = function(sparksEarned, isPersonalBest, rank, prevBest) {
  // sparksEarned  — Sparks awarded for this run
  // isPersonalBest — true if this beats the player's previous best
  // rank           — global leaderboard position
  // prevBest       — previous personal best score (null if first run)
};

Games without scoring call gameOver() with no argument. No leaderboard will appear for the game.

Save system

Sparkade provides cloud saves for every game automatically. There is one save slot per player per game — a JSON blob of up to 64 KB. Use it to persist anything that should survive between sessions: unlocked levels, high scores, settings, inventory, or any other game state.

The save data is fetched in parallel before your game script even loads, so by the time your code runs the data is already available with no network latency. You register a callback, call loadData(), and the data arrives immediately in practice. Writing is fire-and-forget — call saveData() whenever meaningful progress happens and the platform handles debouncing, retry logic, and flushing on exit.

One important detail: _iap is a reserved key on the save object. It contains the player's purchase ownership data and is injected by the platform on every read. You cannot write to it — any _iap key in your saveData() call is stripped automatically. Read from it freely, never write to it.

// Register the callback before calling loadData()
window.onDataLoaded = function(save) {
  const level = save.level || 1;
  const gold  = save.gold  || 0;
};
window.loadData();

// Write — fire and forget, call whenever meaningful progress occurs
window.saveData({ level: 5, gold: 250 });
Guest users (not logged in) receive an empty save {}. Always use fallback defaults for every property you read from the save object.

In-app purchases

Sparkade has a built-in in-app purchase system that lets players spend Sparks on items inside your game. You define what the items are and what they do in your game logic. The pricing, transaction processing, and purchase UI are all handled by the platform — you never touch payment logic.

The flow is straightforward: you register items when managing your game from within the platform (after your game is live), load the catalogue at game start, and call requestPurchase(key) when the player wants to buy something. Sparkade shows its own native purchase overlay with the item name, description, and Spark cost. The player confirms or cancels. Your game receives the result and responds accordingly.

Two item types are available. Permanent items are bought once and owned forever — characters, cosmetics, level unlocks. Consumable items have a quantity that depletes — extra lives, hint packs, power-up bundles. Both types are tracked server-side; ownership arrives on the save object via the _iap key and cannot be spoofed by the client.

Permanent — bought once, owned forever. Characters, level unlocks, skins.
Consumable — quantity-based, can be repurchased. Extra lives, power-up packs, hints.
// 1. Load the item catalogue (returns what the game sells + ownership status)
window.onItemsLoaded = function(items) {
  // items is keyed by your registered item keys, e.g.:
  // {
  //   sword: { name: 'Sword', cost: 100, item_type: 'permanent', owned: false },
  //   lives: { name: 'Extra Lives', cost: 30, item_type: 'consumable', owned: 3 },
  // }
};
window.loadItems();

// 2. Trigger a purchase — Sparkade shows its own overlay, your game waits
window.requestPurchase('sword');
window.onPurchaseResult = function(success, updatedSave) {
  if (success) {
    // updatedSave._iap.sword is now true (permanent)
    // updatedSave._iap.lives is updated quantity (consumable)
    unlockSword();
  }
};

// 3. Spend a consumable (deducts quantity server-side)
window.spendItem('lives', 1);
window.onSpendResult = function(success, updatedSave) {
  if (success) activateExtraLife();
};
Prices are always set in the In-App Purchases panel within your game's My Games screen — never hardcode them in your game. The purchase overlay is owned by Sparkade. Your game calls the function and waits for the result.

Player info

The bridge exposes the current player's Sparkade username via window.getUsername(). This is useful any time your game needs to know who is playing — displaying their name in the UI, identifying them in a multiplayer room, or personalising the experience.

The function returns a string for logged-in players and null for guests. Always guard against the null case — guests can play any game on Sparkade without an account.

const username = window.getUsername();

if (username) {
  // logged-in player — show their name, connect to multiplayer, etc.
  welcomeText.setText('Welcome back, ' + username + '!');
} else {
  // guest — personalisation and multiplayer not available
  welcomeText.setText('Welcome!');
}
Username is the only player identity the bridge exposes. No email, ID, or other account details are available to game authors.

Multiplayer

Sparkade supports real-time multiplayer games. WebSocket connections (wss://) to external game servers are permitted, which means you can use established multiplayer frameworks like Colyseus to power your rooms, matchmaking, and game state sync.

The typical setup is straightforward: you host a Colyseus server (or any WebSocket-based server), connect to it from your game using window.getUsername() as the player identity, and use your server to synchronise game state between players. The platform handles everything else — session management, saves, and IAP work exactly the same way they do in single-player games.

// Connect to your Colyseus server using the player's Sparkade username
import { Client } from 'colyseus.js';

const username = window.getUsername();

if (!username) {
  // Guest players can't join multiplayer — show a sign-in prompt
  showSignInPrompt();
  return;
}

const client = new Client('wss://your-colyseus-server.com');
const room   = await client.joinOrCreate('game_room', { username });

room.onStateChange((state) => {
  // update your Phaser scene from server state
});
Colyseus has a free hosting tier suitable for small games. You can also self-host on any Node-compatible platform. Your server URL just needs to serve over wss://.

Things to keep in mind

Use window.getUsername() as the player identifier in your multiplayer room — it is stable and unique per Sparkade account.
Always handle the null case — guests cannot participate in multiplayer sessions that require identity.
Cloud saves and IAP still work in multiplayer games — the bridge handles them the same way regardless of whether a WebSocket connection is open.
Your Colyseus server is separate infrastructure you own and host — Sparkade does not manage it.
Include the Colyseus client library in your game.js as a bundled import, or embed it as a self-contained script — external script tags are not supported.

Managing game details

Once your game is live, you manage it entirely from within the Sparkade app. Open your profile, go to the My Games tab, and tap any game to open its management panel. Everything you need is in here.

Updates to your live game — new file versions, description changes, swapping the thumbnail — happen immediately. This means you can iterate quickly: fix a bug, upload the corrected file, and it is live within seconds.

Game Details

From the Game Details panel you can update:

Description — up to 300 characters shown on the game card in the feed
Game file — upload a new version of your game.js. The version number increments automatically.
Thumbnail — the static PNG shown in the feed before the player taps play
Preview — an optional GIF or PNG shown as an animated preview in the feed
Updating the game file does not require a new submission review — file updates on live games go live immediately. Description and image changes are also immediate.

Managing in-app purchases

IAP items live in the In-App Purchases panel within your game's manage screen. You create items here after your game is live — which means you can add monetisation to a game that launched without it, or expand your item catalogue over time without resubmitting the game file.

Each item has an item key — a short snake_case identifier like extra_lives or starter_pack. This key is what your game code uses to reference the item in requestPurchase() and spendItem(). It is generated automatically from the item name when you create it, and it cannot be changed after creation. Choose it carefully.

One deliberate constraint: items cannot be permanently deleted once a player has purchased them. Deleting an item would break ownership for anyone who bought it. Instead, use the pause toggle to remove an item from sale — paused items stay in your catalogue and ownership is preserved, but new purchases are blocked.

Adding an item

Tap Add Item and fill in the details. The item key is auto-generated from the name — it becomes the identifier your game code references in requestPurchase(key) and spendItem(key, amount). The key cannot be changed after creation.

Editing an item

You can update the name, description, and Spark cost of any item at any time. The item type (permanent vs consumable) cannot be changed after creation.

Pausing an item

Items can be paused to remove them from sale without deleting them. A paused item is no longer shown in the purchase overlay and cannot be bought — but existing ownership is preserved. Players who already own a paused permanent item keep it. Players who already own a paused consumable keep their quantity.

Items cannot be permanently deleted once any player has purchased them. Use the pause toggle to remove an item from sale instead.

Earnings

Every time a player spends Sparks on an in-game item inside your game, you earn a percentage of that transaction. This is your creator cut.

The rate you earn depends on your membership tier. Free accounts receive a base cut on every sale. Pro members earn a significantly higher rate — the difference compounds meaningfully as your game attracts more players and purchases grow.

Free — base creator cut on all in-game Spark purchases.
Pro — increased creator cut rate on all in-game Spark purchases.

Your current cut rate is shown in the earnings panel alongside your totals, so you always know exactly what percentage you are retaining.

Tracking your earnings

The earnings panel inside your game's manage screen gives you a full breakdown of performance — both at the game level and per item. You can see total Sparks earned across all time, earnings for the current week compared to last week, and a trend indicator showing whether revenue is growing or declining.

Each IAP item is listed individually with its unit cost, total units sold, and the Sparks you have earned from it specifically. This makes it straightforward to see which items are driving revenue, which are not converting, and where to focus your game updates or pricing adjustments.

The earnings panel is accessible from the My Games tab on your profile. Tap any live game and open the Earnings section from its manage screen.

Analytics

Every game gets a built-in analytics panel accessible from its manage screen under My Games. Analytics are split into two tiers — a free overview available to all developers, and deeper player insights gated behind Pro membership.

Free analytics

All developers can see lifetime totals for views, plays, likes, and comments on their game, plus three conversion metrics derived from those numbers:

Play-through rate — the percentage of views that became plays.
Engagement rate — the percentage of plays that earned a like.
Comment rate — the percentage of plays that resulted in a comment.

Pro analytics

Pro members unlock a deeper set of player insights on top of the free overview:

Unique players — distinct accounts that have played your game.
Returning players — the percentage of players who came back for a second session or more, shown as a retention donut.
Average sessions per player — how many times the average player has played.
New players this week — first-time players in the last 7 days.
Daily play chart — completions across the last 7 days, with your peak day highlighted.
Day-of-week pattern — all-time activity by day, useful for timing updates and announcements.
Score spread — average and top score, with a difficulty indicator. Leaderboard games only.
Score submission rate — the percentage of plays that resulted in a submitted score. Leaderboard games only.
Analytics update in real time. The Pro analytics section is visible to all developers but blurred and locked — upgrading to Pro from the shop unlocks it immediately without needing to resubmit your game.

Versioning

Every time you upload a new game file, Sparkade automatically increments the version number. You do not manage versions manually — the platform tracks them for you so you always know which build is live and can see the history in your My Games panel.

Every time you upload a new game file, the version number increments automatically (v1.0.0 → v1.1.0 → v1.2.0). Version numbers are visible on the game card in your My Games tab and in the manage panel header.

There is no review process for file updates on live games. The new file goes live immediately on upload. If an update introduces a bug or a failed validation check, you can upload a corrected file straight away.

Automated analyser

There is no manual review process on Sparkade. When you submit a game, it is checked instantly by an automated analyser. If it passes, it goes live in the feed immediately. If it fails, you get a specific error telling you exactly what to fix.

The analyser is what keeps the platform safe for players. It enforces two things: required bridge calls are present, and banned patterns are absent. It cannot catch runtime bugs or broken gameplay — that is on you to test before uploading. The checks are intentionally strict on network access and storage because those are the vectors that could harm players.

The analyser runs in milliseconds but it is static — it cannot detect runtime errors, broken controls, or gameplay issues. Test your game on a real phone before submitting.

Analyser errors

When the analyser rejects a file, you get a specific error message — not a vague failure. Fix the flagged issue and resubmit. These are the errors you are most likely to encounter.

Missing window.gameStarted
The string 'window.gameStarted' was not found anywhere in your file. Add the call — even if it's in a comment the check will pass, but it must actually be called when gameplay begins.
Missing window.gameExit
The string 'window.gameExit' was not found anywhere in your file. Wire it to an exit button so players can always return to the feed.
Disallowed pattern detected
Your file contains a banned pattern. All assets must be embedded; no external calls are permitted.
File too large
The file exceeds 2 MB. Optimise your base64 assets or reduce texture sizes.