What an HTML5 playable actually is, technically

Strip away the marketing and a playable ad is a self-contained document that a mobile app's ad SDK loads into an embedded WebView — no network access assumed, because the device may be offline or the network may sandbox it. That constraint drives everything about the format:

  • No runtime external dependencies. Every image, sprite sheet, audio clip, font, and script has to already be inside the file (or ZIP) the network serves — not fetched from a CDN at play time.
  • A single DOM/JS context. There's one document, one script scope. Whatever engine produced the content (Unity's WebGL/HTML5 export pipeline, in this case) has to run inside that one context via its own bundled JS, canvas, and asset-loading code — not as a native binary.
  • A defined entry point. The ad SDK needs to know exactly which file to load first. Every network in practice enforces this by requiring a specific filename or a fixed folder layout, not just "an HTML file somewhere in the ZIP."
  • A network-recognized click signal. Because the WebView is sandboxed, a plain <a href> or window.location redirect usually does nothing useful — the SDK has to intercept the click through its own API.

A Playworks/Luna export satisfies the first two points by default — it's already HTML5, already self-contained inside its own ZIP. Where it falls short is the last two: it's built to one shape, and every network defines that shape differently.

Why a single Unity export needs reshaping per network

Playworks/Luna generates one build for one target. Ad networks don't share a target. Looking at the actual requirements networks publish, the differences aren't stylistic — they're structural:

NetworkPackage shapeEntry fileExternal requests
Google AdsZIP, ≤5 MB, ≤512 filesindex.htmlOnly Google-approved hosted libraries (ExitApi, Fonts, jQuery/CreateJS/Greensock)
AppLovinSingle HTML file, ≤5 MBapplovin.htmlProhibited — everything inlined
MetaSingle HTML file, ≤5 MB (2 MB recommended)meta.htmlProhibited — everything inlined
Unity AdsSingle HTML file, ≤5 MBunity.htmlProhibited — everything inlined
VungleSingle HTML file, ≤5 MBvungle.htmlProhibited — everything inlined
MintegralZIP, ≤5 MB, archive/folder/HTML names must matchmintegral/mintegral.htmlLocal ZIP resources allowed
TikTokZIP, ≤5 MB, first-level config.json requiredindex.html (first-level)Prohibited — needs Playable-sdk.js instead

Notice the split: Google, Mintegral, and TikTok want a ZIP with a specific internal layout; AppLovin, Meta, Unity Ads, and Vungle want a single monolithic HTML file with nothing external at all — not even a relative-path image reference. A Playworks/Luna export, being built for one destination, is naturally one or the other. Getting it correct for the other shape means restructuring the package, not just re-zipping it.

What actually changes during HTML5 conversion

Asset inlining: from file references to base64/data URIs

For AppLovin, Meta, Unity Ads, and Vungle, every image, audio clip, and font referenced by <img src="...">, url(...), or an Audio() call has to be rewritten as an embedded data URI (data:image/png;base64,...) or an equivalent encoded blob decoded by JS at load time, directly inside the one HTML file. There is no folder structure left for the browser to resolve — because prohibitsExternalRequests means there's no "load" step happening outside the document at all. For Google and Mintegral, assets can stay as local relative-path files inside the ZIP instead, since those networks explicitly allow ZIP resources — so the conversion there is closer to careful bundling than full inlining.

Click-through wiring: clickTag, MRAID, and network-native SDKs

This is the part that varies the most and breaks the most creatives when it's wrong. AppLovin and Unity Ads both require MRAID 2.0 and expect the CTA to call mraid.open(url), but only after the SDK signals MRAID is ready — calling it earlier, or using it against a network that doesn't expect MRAID at all, causes the click to silently fail. Vungle's primary path is window.parent.postMessage('download','*'), with MRAID open only as a fallback. Mintegral expects window.install(), again falling back to mraid.open(url), plus a separate window.gameEnd() callback networks test independently. TikTok explicitly prohibits the MRAID pattern and instead requires its own Playable-sdk.js and a window.openAppStore() call. Meta requires FbPlayableAd.onCTAClick() and explicitly disallows auto-redirects or window.open. Google uses its own ExitApi.exit() loaded from a Google-hosted script. None of this is one clickTag variable dropped into every build — it's six or seven distinct call conventions, several of which actively conflict with each other (MRAID vs. explicitly-prohibited MRAID, for instance).

Structural enforcement: DOCTYPE, orientation, and entry naming

Google specifically requires a proper DOCTYPE, an <html>/<body> structure, and an orientation meta tag on the entry file — a raw Unity WebGL export doesn't always emit these in the form Google's scanner expects. TikTok instead wants orientation declared in a first-level config.json rather than in the HTML at all. AppLovin requires the build to support both device orientations. Mintegral requires the ZIP's archive name, top-level folder name, and HTML filename to all match — a naming constraint that has nothing to do with functionality and everything to do with how Mintegral's ingestion pipeline unpacks the file.

Size compliance

Every network caps creatives at 5 MB, with Meta and Unity Ads specifically recommending 2 MB. Inlining assets as base64 inflates their size by roughly a third over the raw binary, so a build that fits comfortably as a ZIP with linked files can blow past the cap once it's converted to a single self-contained file for AppLovin or Meta. Compression has to happen alongside inlining, not before it — see playable ad compression for what that involves.

How PlayableKit automates this

PlayableKit takes one Playworks/Luna export and applies the network-specific transformation described above per target: inlining or bundling assets according to whether the network allows ZIP resources or requires a monolithic file, writing the correct click-through call (ExitApi, MRAID, SDK-native, or postMessage) with the readiness checks each one needs, enforcing entry-file naming and folder/archive matching, injecting orientation declarations in whichever form the network expects (meta tag vs. config.json), and compressing to stay under each network's byte cap. The output is a separate, correctly shaped package per network from the same source build — not one build hopefully-compatible with all of them.

Worth knowing: this page covers the technical mechanics of the HTML5 conversion itself. For the broader picture of what converting a Playworks/Luna export for multiple networks involves end to end, see the playable ad converter overview.