The problem

Google Ads checks two separate ceilings on every playable ZIP upload: 5 MB total size and 512 files inside the archive. Most people only know about the first one, because it's the number that shows up in every spec sheet and it's intuitive — bigger creative, bigger file, hits the cap. The 512-file rule gets far less attention, but it's enforced with exactly the same strictness. A ZIP can sail under 5 MB and still be rejected outright because it contains 700 files.

This is a genuinely common way for a Google playable upload to fail, and it's confusing the first time it happens because nothing about the creative "looks" too big. The rejection isn't about weight — it's about count.

Why this happens with Unity/Playworks/Luna exports specifically

A Unity Playworks/Luna export is built to run correctly in a browser, not to minimize file count. Depending on how the export is configured, a build can unpack into a large number of small, individual files rather than a handful of bundled ones:

  • Texture atlases split apart. If sprites aren't packed into shared atlases before export, each texture can land in the archive as its own file — a UI with a few dozen individual icons and button states adds up fast.
  • Per-clip audio files. Sound effects and voice lines often export as one file each rather than a combined audio sprite.
  • Font and engine support files. Runtime scripts, font subsets, and engine metadata files each count as one entry in the ZIP, on top of the actual creative assets.
  • Folder mode specifically. When Playworks/Luna exports assets as a loose folder structure rather than a single packed bundle, every asset that would otherwise be inlined or concatenated instead becomes a standalone file in the archive — multiplying the count without changing the total byte size much at all.

None of this is unusual or a sign of a broken build. It's simply how engine exporters default to laying out assets, and Google's 512-file cap is a packaging rule that most exporters were never built with in mind. Per src/networkSpecs.js, Google's actual requirement is explicit: "ZIP must be 5 MB or smaller and contain no more than 512 files." Both conditions are checked, and either one failing is enough to reject the upload — independent of the other. It's also worth remembering this cap is specific to Google's ZIP packaging; it sits alongside Google's other structural rules — index.html as the root entry file, a valid <!DOCTYPE> with proper <html>/<body> tags, an orientation <meta> tag, local relative asset paths (plus Google's small external allow-list for its ExitApi script, Google Fonts, and Google-hosted jQuery/CreateJS/GreenSock), and clickTag wired to ExitApi.exit() — which are covered in full on the Google Playable Ads reference.

How to check your actual file count

Don't guess — count. Extract the ZIP and count every file in the archive, including nested folders, engine scripts, and anything that isn't a "real" visual asset. On most systems that's a quick file-manager check or a one-line terminal command; the number that matters is the total entry count inside the ZIP, not how many assets you think you added. If you're not sure, run the archive through PlayableKit's free validator, which checks a ZIP's file count against Google's 512 ceiling directly, alongside the other structural requirements, with no signup required.

Fix approaches

  • Atlas your textures. Pack individual sprites and UI elements into shared texture atlases before export. This is the single biggest lever — collapsing dozens of loose image files into one or two atlas sheets can cut file count dramatically without changing total byte size much.
  • Combine audio into sprites. Merge short sound effects into a single audio sprite file played at different offsets, instead of shipping one file per clip.
  • Avoid folder-mode exports for Google specifically. If your exporter offers a choice between a loose folder structure and a packed/bundled output, the packed option produces far fewer standalone files for the same creative.
  • Embed rather than reference where Google's rules allow it. Google permits local relative paths inside the ZIP (it doesn't require full base64 inlining the way AppLovin or Meta do), but reducing the number of separate referenced files — by embedding small assets directly in the HTML/JS as data URIs where practical — still reduces the entry count in the archive.
  • Cut asset variants you don't need. Duplicate assets for unused states, leftover debug textures, or multiple resolution variants bundled "just in case" all count toward the same 512 cap. Trim what the shipped creative doesn't actually use.

PlayableKit takes a Unity Playworks/Luna export and repackages it for Google Ads with this exact cap enforced automatically — bundling and structuring the output so it clears both the 5 MB and 512-file limits before you upload, alongside the clickTag/ExitApi.exit() wiring and orientation meta tag Google also requires. See the full walkthrough at Export Unity Playworks/Luna to Google Ads.

Worth knowing: the 512-file check and the 5 MB check are independent — fixing one doesn't fix the other. Run both checks every time you add an asset, not just before the first submission; a single extra texture late in production is a common way a previously-compliant build quietly crosses the line.