Skip to content

Release checklist

Run these on the build folder (dist/) before zipping it. Each item says why, so a game with an unusual build can apply the reason. The bundle is served from https://<slug>.<play domain>/<slug>/<version>/ through the CDN and from the preview host under a cookie scoped to that path; both carry the CSP of the lifecycle page.

  • index.html sits at the root of the folder. The upload refuses a zip without it at the root, and a zip of the folder itself puts it one level down: zip the content.
  • No .. in a path, no symbolic link, no encrypted entry, no file named _upload.zip. The upload refuses them.
  • Zip at most 200 MB, at most 500 MB unpacked, at most 10,000 files, at most 64 MB per file. A few MB loads fast on a phone.
  • No source maps unless you want them public.

Check the archive

Terminal window
cd dist && zip -r ../gloop-1.4.0.zip . -x '*.map' && cd ..
unzip -l gloop-1.4.0.zip | head -5 # index.html first, at the root
du -sh dist && find dist -type f | wc -l
  • Every URL in HTML, CSS and JS is relative: assets/x.png, ./main.js. Vite needs base: './'.
  • No URL starts with /. On the CDN it leaves /<slug>/<version>/; in the preview it also loses the cookie and answers 404.
  • Code that builds URLs at run time uses new URL('assets/x.png', document.baseURI) or import.meta.url, never location.origin + '/...'.
  • Workers and WASM load from relative paths too: new Worker(new URL('./w.js', import.meta.url), { type: 'module' }).

Find root-relative URLs

Terminal window
grep -rnoE '(src|href)="/[^/]' dist --include='*.html'
grep -rnoE "url\(/[^/]" dist --include='*.css'
grep -rnoE "[\"'\`]/(assets|src|img|audio|data)/" dist --include='*.js' | head
  • No <script src="https://...">: the CSP blocks it. The SDK loads the CrazyGames, Poki and Playgama scripts on their own portal; remove the tags the old build had.
  • No inline <script> with code, no onclick="" attribute, no eval, no new Function, no setTimeout with a string. The policy has no unsafe-inline or unsafe-eval for scripts: they throw.
  • No fetch, XMLHttpRequest, WebSocket or EventSource to a host other than the bundle’s own and the API. Analytics and error reporting go through sdk.track().
  • Fonts, images, audio and data files are inside the bundle. No Google Fonts link, no CDN library.
  • The SDK is either bundled (ESM) or loaded by a relative script tag, vendor/filudi-sdk-<version>.iife.js, before the game’s script.
  • sdk-trace.js and any other playground helper are gone.

Find what the CSP will block

Terminal window
grep -rnE "<script[^>]+src=.https?:" dist --include='*.html'
grep -rnE '<script>[^<]|<script type="module">[^<]| on[a-z]+="' dist --include='*.html'
grep -rnE '\beval\(|new Function\(' dist --include='*.js' | grep -v filudi-sdk | head
grep -rnE 'fonts.googleapis|cdn\.|unpkg|jsdelivr|sdk-trace' dist | head
  • The game fills its frame: it reads innerWidth and innerHeight or a ResizeObserver, not screen.width, and redraws on resize.
  • The orientation declared on the game (portrait, landscape or any) matches what the layout handles.
  • mobile and minutes are set on the game.
  • The game does not draw its own fullscreen button on CrazyGames (result.host === 'crazygames'); elsewhere a button calls sdk.fullscreen().
  • Audio starts after a user gesture.
  • A click in the game gives it the keyboard: a pointerdown handler that calls preventDefault() also calls canvas.focus(), with tabindex="0" on the canvas. Check it: click outside the game, click in the game, press an arrow key.
  • sdk.init({ gameId, version }): gameId is the game’s slug in the console, version the version you upload.
  • sdk.ready() after the first frame, once.
  • Gameplay brackets around real play only; mute on ads.on('start'); reward on completed only.
  • Every board passed to leaderboard.submit() is declared in the console with its bounds.
  • The CrazyGames leaderboard key comes from a build variable and is not in the repository.
  • The game ran on each host of the playground with “Production CSP” on and no CSP violation line.

Upload the zip, preview it, request its review: your first bundle. Tag the game’s repository with the version you uploaded, so a rollback (activating the previous version again) has a source to match.