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
game.js file (max 2 MB).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.mdDescribe your game idea. The AI fetches the relevant skills, builds the complete game file, and hands you a ready-to-upload game.js.
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.
wss:// WebSocket connections to your game server are supportedScene 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 deathScoring
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 });{}. 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.
// 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();
};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!');
}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
});wss://.Things to keep in mind
window.getUsername() as the player identifier in your multiplayer room — it is stable and unique per Sparkade account.null case — guests cannot participate in multiplayer sessions that require identity.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:
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.
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.
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.
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:
Pro analytics
Pro members unlock a deeper set of player insights on top of the free overview:
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.
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.
