Why web game portals reject your WebGL build
The question: "Why does my Unity WebGL build keep getting rejected by HTML5 game portals?"
Almost every rejection comes from a small set of causes, and almost none of them are about whether the game is fun. Portals run a checklist. The build either clears the numbers and the behaviour rules or it does not, and the reviewer often sends back nothing more than a rejection.
This page is the checklist, grouped by the thing that actually fails. Each portal's own requirements are the authority and they change, so treat the numbers here as where to look rather than as a substitute for reading them.
1. The build is too big, or too big too early
Two different limits, and confusing them is the most common near miss. Portals care far more about how much has to download before the player can interact than about total size.
| Portal | Initial download | Total size |
|---|---|---|
| CrazyGames | under 50 MB, and under 20 MB to qualify for the mobile homepage | 250 MB with the SDK, 50 MB without |
| YouTube Playables | must be under 30 MB, should be under 15 MB | must be under 250 MB |
| Yandex Games | not measured separately | 100 MB unarchived, total |
| Poki | no published hard cap, but load time is reviewed | no published hard cap |
Note how the initial figure is defined, because it decides what counts. On
CrazyGames it is measured from load start until your first gameplay-start SDK
event. On YouTube Playables it is everything downloaded from page load to the
gameReady() call. Both definitions mean the same thing in practice: the
loading screen is inside the measurement, so deferring an asset until after the
splash screen does not help unless the player can already play.
What actually moves these numbers on a Unity WebGL build:
- Compression. Brotli over Gzip. Uncompressed builds are frequently rejected outright, and some portals will not run a development build at all.
- Addressables. Marking assets Addressable pulls them out of the initial
download. Watch for direct references, which quietly pull the asset back in,
and remember to upload the generated
StreamingAssetsfolder alongside the build. - Engine code stripping. High managed stripping plus stripped engine code.
- Textures and audio. Crunch compression on textures, mono audio, no mipmaps
on sprites, and
.jpgwherever transparency is not needed. - Code optimisation set to size, not to shorter build time.
2. A file count or file name breaks the bundler
These are trivially avoidable and still cause rejections, because they fail before a human ever looks at the game.
- File count caps. CrazyGames allows 1500 files; YouTube Playables allows 8000. A Unity build with unpacked Addressables can pass either without warning.
- Individual file caps. YouTube Playables requires every single file to be under 30 MB, and recommends under 512 KB.
- Absolute paths fail everywhere. Every portal serves your game from a path and origin you do not control, frequently inside an iframe on a localised domain. An absolute URL to your own host, or to the storage bucket you tested from, will not resolve. Use relative paths only.
- File names. YouTube Playables restricts bundle file names to alphanumerics,
_,-and.. Yandex requiresindex.htmlat the archive root and no spaces or Cyrillic characters anywhere in file or folder names.
3. It works in Chrome and nowhere else
Reviewers test on more than one browser, and a game that only works on Chromium gets flagged or disabled on the browsers that fail.
- Safari is where builds die. It differs on WebGL context creation, on audio autoplay policy, and on touch event handling.
- iOS audio needs an explicit resume. The AudioContext enters an
interruptedstate when the game is backgrounded. CallaudioContext.resume()from inside a real user gesture (touchend,click) when the state issuspended. Doing it on a timer does not work, because the browser requires the gesture. - Memory ceilings are real. YouTube Playables rejects a peak JavaScript heap over 512 MB, and notes plainly that a 512 MB heap crashes on iPhones. CrazyGames tests on a 4 GB Chromebook.
- Device pixel ratio. Some portals force a device pixel ratio of 1 on iOS and low-memory Android. If your UI is only legible at native ratio, it will be reviewed as illegible.
4. It does not adapt to the frame it is given
Your game does not choose its viewport. It is placed in one.
- Do not lock orientation where the portal forbids it. YouTube Playables prohibits locking device orientation or posture outright, and requires the game to be playable at every aspect ratio from 9:32 through 32:9, adjusting automatically when the viewport changes and keeping game state across a resize.
- Do not ship your own fullscreen button. CrazyGames prohibits custom in-game fullscreen buttons; Yandex already provides one. A duplicate control is a rejection reason on its own.
- Watch the safe area on mobile: notches and dynamic islands. Use the CSS
env(safe-area-inset-*)variables rather than fixed padding. - Suppress the browser's own behaviour that fights the game: page scroll on arrow keys and space, the context menu on right click, and text selection on long press. Yandex explicitly requires that a long tap on the game field does not select it or open a context menu.
5. The SDK lifecycle is missing, wrong, or fired at the wrong moment
This one is invisible in local testing and is the reason a build that plays perfectly still fails review. Portals do not just want their SDK present; they want specific events at specific moments, and they measure your game with them.
- The loading signal defines your initial download. If you never fire it, or fire it late, your build is measured as larger than it is.
- The ready signal must mean ready. YouTube Playables requires
gameReady()exactly once and forbids calling it while a splash or loading screen is still showing, because that call removes their loading spinner. - Gameplay start and stop must bracket real gameplay, including unpause and resume after an ad, not just the first level load.
- Pause and resume must come from the SDK, not from the web platform.
YouTube Playables specifically prohibits using the Page Visibility API for
this and requires their
onPauseandonResumecallbacks. - Fire signals generously. On Poki, every commercial break you signal is an opportunity their backend may or may not fill; rationing the signal costs revenue and does not reduce ad load.
The awkward part is that these events have different names, different arities and different required orderings per portal, so the lifecycle is the piece most often implemented four times and got subtly wrong once. If you would rather write it once, the Yes2SDK lifecycle and game modules expose a single sequence that maps onto each portal's own calls, and the Inspector fails the check when a required event is missing or fires in the wrong order rather than leaving it to a reviewer to notice.
6. Things that are simply not allowed
Worth a read-through, because each is a hard stop rather than a quality note.
- External links out of the game. Yandex permits links only to your own games in its catalogue, through its SDK. YouTube Playables prohibits clickable links to external content entirely, along with in-game sharing prompts, additional user agreements, and any in-game exit or quit button. CrazyGames allows store links on desktop as a non-primary call to action, and never App Store links.
- Anything that looks like a login or a QR code. YouTube Playables prohibits content resembling or functioning as a login or account-creation screen, and graphical content resembling or functioning as a QR code, and prohibits collecting personal information at all.
- Off-platform monetisation. Every portal requires ads and purchases to go through its own SDK. A third-party ad network in the build is a rejection.
- Obfuscated code. YouTube Playables prohibits obfuscation, though
minification is fine, and may decline a game it cannot analyse because of
WebAssembly,
eval(), or Web Workers.
7. Load time, measured by a bored reviewer
Under 5 seconds to interactive is the target YouTube Playables states. CrazyGames requires games loading external files to reach gameplay within 20 seconds, and reports that conversion (players who last at least a minute) tracks with load under 10 seconds and a build under 20 MB.
Keep Awake and Start light. Work moved off the first frame is work the
reviewer does not sit through.
Check it before a reviewer does
Nothing above requires guessing. Every item is either a number you can measure or a behaviour you can observe, and the failure mode worth designing against is the silent one: a build that plays fine on your machine, misses a lifecycle event or a size cap, and comes back rejected with no explanation.
That is the specific problem the Yes2Games tooling is built around. One integration ships your HTML5 game to every supported web game platform. You wire the lifecycle, ads, saves and the rest once against a single API, and the QA Inspector replays your build against each portal's actual rejection rules before you submit, so a missing gameplay-start event or an over-cap initial download shows up as a failed check rather than as a rejection email.
If you would rather have your coding assistant do the wiring, the MCP server serves these same per-portal requirements as structured data, so the assistant reads the rules instead of guessing at them.
Where to read the rules yourself
Each portal's requirements are the authority, and they move:
- CrazyGames requirements
- YouTube Playables certification
- Yandex Games requirements
- Poki SDK documentation
The per-portal requirement sheets on this site are generated from the same rule set the Inspector checks against, so what you read and what gets verified do not drift. Every supported platform has one, listed in the docs index, and the API overview carries the current support matrix.