This extension provides an object-based Lua API for Unity LevelPlay mediation on Android and iOS. It follows the SDK 9 lifecycle: initialize the SDK, create an ad object with an ad-unit ID, then explicitly load and show that object.
Add a released archive to the Dependencies field in game.project:
https://github.com/defold/extension-levelplay/archive/refs/tags/<version>.zip
Choose a version from Releases, replace <version>, then select Project → Fetch Libraries.

Add a [levelplay] section and enable only the adapters used by your dashboard configuration. The example intentionally enables only Unity Ads:
[levelplay]
unity_ads_android = 1
unity_ads_ios = 1
ios_tracking_usage_description = Your data will be used to provide a more relevant advertising experience.
Available adapter prefixes are:
applovin, aps, bidmachine, bigo, chartboost, dt_exchange, admob,
hyprmx, inmobi, liftoff, line, meta, mintegral, mobilefuse, moloco,
ogury, pangle, pubmatic, smaato, superawesome, unity_ads, verve, vk,
yandex, yso
Append _android or _ios to a prefix. Tencent is available as
tencent_ios. When enabling AdMob on iOS, also set admob_ios_appid.
The current Android Google Mobile Ads SDK obtains the app ID from your
LevelPlay dashboard rather than Android manifest metadata.
Amazon Publisher Services must be configured with its app ID so the extension can initialize APS before LevelPlay:
[levelplay]
aps_android = 1
aps_android_app_id = your-aps-android-app-id
aps_ios = 1
aps_ios_app_id = your-aps-ios-app-id
The Android template includes Google advertising identifier dependencies and
the AD_ID permission by default. Set android_google_identifiers = 0 for an
Amazon build or a primarily child-directed build that must omit these
identifiers. The disabled path also emits the Android manifest removal rule so
an enabled adapter cannot merge AD_ID back in. Set COPPA and all
network-required device-ID opt-outs before initialization.
The Mintegral switch currently selects its verified overseas SDK and repository, not the separate China artifact family. Smaato and Meta network-security requirements, APS manifest components and Kotlin runtime, and Mintegral’s RecyclerView dependency are generated automatically.
Follow the current network integration guide for dashboard setup and any network-specific privacy metadata. For Chartboost, set its required COPPA value before initialization:
levelplay.set_metadata("CHARTBOOST_COPPA", is_child_directed and "YES" or "NO")
The extension requires Android API 24 or newer and targets API 36. Its iOS native templates set the deployment target to iOS 15, matching the current Defold iOS toolchain.
The iOS template always enables the transport setting required by the mediated SDKs, the LevelPlay attribution endpoint, Unity’s current primary SKAdNetwork ID, and the primary IDs recorded in this repository for each enabled adapter.
Apple requires the complete current set for every network your application
uses. Partner-, account-, region-, and app-specific SKAdNetwork and
AdAttributionKit identifiers cannot be inferred from an adapter switch. Before
shipping, collect the complete lists from Unity’s iOS network guides and each
enabled partner. Supply a custom project Info.plist through Defold’s iOS →
Info.plist project setting containing the complete SKAdNetworkItems list;
Defold merges that base manifest with this extension’s iOS manifest. Review the
merged Info.plist in the final IPA. See Defold’s
manifest merge documentation
and Unity’s
SKAdNetwork guidance.
An app key identifies the application and is passed only to levelplay.init().
An ad-unit ID identifies one dashboard ad unit and is passed to create_interstitial_ad(), create_rewarded_ad(), or create_banner_ad(). An app key and an ad-unit ID are not interchangeable. Each ad object retains its own ad-unit ID, listener, loading state, and integer handle.
The repository example uses Unity’s official demo credentials:
| Platform | App key | Interstitial | Rewarded | Banner |
|---|---|---|---|---|
| Android | 25b63cf85 |
h3xw38h9214adgxo |
syz3d8ekts22q0or |
4fpetq4lhe5lsw3e |
| iOS | 25c43a4a5 |
obg6ohwts3y690ks |
l1quzz1xmmdhw5er |
xc2bsuntn9ea734t |
Replace every value with credentials from your own LevelPlay dashboard before release.
Collect privacy choices in your own consent flow and apply them before initialization. The sample values below are placeholders, not a consent-management implementation:
levelplay.set_gdpr_consent(user_granted_gdpr_consent)
levelplay.set_ccpa(user_opted_out_of_sale_or_sharing)
levelplay.set_coppa(is_child_directed)
-- When Meta Audience Network is enabled:
levelplay.set_meta_limited_data_use(use_meta_ldu, 0, 0)
local function initialize_after_tracking(status)
-- Meta has a separate iOS flag; call this before init when Meta is enabled.
levelplay.set_meta_advertiser_tracking(
status == levelplay.TRACKING_STATUS_AUTHORIZED
)
levelplay.init(APP_KEY, user_id)
end
local status = levelplay.get_tracking_authorization_status()
if status == levelplay.TRACKING_STATUS_NOT_DETERMINED then
-- In MSG_TRACKING, call initialize_after_tracking(message.event).
levelplay.request_tracking_authorization()
else
initialize_after_tracking(status)
end
For GDPR, true means consent was granted. For CCPA and related US privacy signals, true means the user opted out of sale or sharing. For COPPA, true flags the user as child-directed. Read Unity’s current regulation settings for Android and iOS, obtain legal guidance appropriate to your application, and set network-specific metadata before init() when required.
Meta’s Limited Data Use setting is not represented by LevelPlay’s scalar
privacy methods, so set_meta_limited_data_use() invokes the enabled Meta SDK
directly. Pass 0, 0 to let Meta determine country and state, or pass the
codes required by Meta’s current documentation.
set_meta_advertiser_tracking() applies Meta’s separate iOS tracking flag.
Both functions return false if Meta is not linked or initialization has
already started.
On iOS, request App Tracking Transparency before init() if mediation
partners should receive the IDFA. The result is delivered through
MSG_TRACKING; get_tracking_authorization_status() returns the current
state. Provide a clear ios_tracking_usage_description in game.project.
When Meta’s mixed-audience treatment applies, set its documented
Meta_Mixed_Audience metadata value before initialization as well.
Install one callback before initializing. Create ad objects only after EVENT_INIT_SUCCEEDED:
local interstitial
local rewarded
local banner
local function levelplay_callback(self, message_id, message)
if message_id == levelplay.MSG_INIT then
if message.event == levelplay.EVENT_INIT_SUCCEEDED then
interstitial = levelplay.create_interstitial_ad(INTERSTITIAL_AD_UNIT_ID)
rewarded = levelplay.create_rewarded_ad(REWARDED_AD_UNIT_ID)
banner = levelplay.create_banner_ad(BANNER_AD_UNIT_ID, {
size = levelplay.BANNER_SIZE_ADAPTIVE,
position = levelplay.BANNER_POSITION_BOTTOM,
respect_safe_area = true,
})
else
print("LevelPlay init failed", message.error_code, message.error_message)
end
end
end
levelplay.set_callback(levelplay_callback)
levelplay.init(APP_KEY, "example-user")
Initialization reports both success and failure. Do not create or load ad objects merely because init() was called.
Create an ad object once after initialization and reuse it throughout the
session. The object is bound to the ad-unit ID supplied to its
create_*_ad() function. It owns that ad unit’s listener, loading state, and
handle.
Creation parameters are not per load or show. They remain attached to the returned handle for its entire lifetime:
| Parameter | Applies to | When supplied | Lifetime |
|---|---|---|---|
ad_unit_id |
All formats | First argument to create_*_ad() |
Fixed for the object |
bid_floor |
All formats | Second argument for interstitial/rewarded; banner options field |
Fixed for the object and every load it performs |
size, position, respect_safe_area |
Banner | Banner options fields |
Fixed for the banner view |
placement |
Banner | Banner options field |
Fixed for the banner and used for reporting |
placement |
Interstitial/rewarded | Optional argument to show_*_ad() |
Selected separately for each impression |
bid_floor is the minimum eCPM in USD. Omit it to use the dashboard and
mediation defaults. If an ad-unit ID, bid floor, or banner creation option must
change, destroy the handle and create a new object. Do not recreate an object
merely to load or show another impression with the same configuration.
For example, the optional bid floor is a positional argument for fullscreen objects but a field in the banner options table:
local interstitial = levelplay.create_interstitial_ad(
INTERSTITIAL_AD_UNIT_ID,
1.25 -- Minimum eCPM in USD for every load by this object.
)
local rewarded = levelplay.create_rewarded_ad(
REWARDED_AD_UNIT_ID,
1.25
)
local banner = levelplay.create_banner_ad(BANNER_AD_UNIT_ID, {
size = levelplay.BANNER_SIZE_ADAPTIVE,
position = levelplay.BANNER_POSITION_BOTTOM,
placement = "HomeScreen", -- Reporting placement for this banner object.
bid_floor = 1.25,
respect_safe_area = true,
})
See Unity’s price-floor documentation for its current auction behavior.
A placement is not an object and is not created by the extension. It is an optional dashboard name passed when showing a ready fullscreen ad. Placements let the same ad-unit object report different presentation points and apply placement-specific rewards, pacing, and capping:
levelplay.show_interstitial_ad(interstitial, "LevelComplete")
levelplay.show_rewarded_ad(rewarded, "DoubleCoins")
Interstitial and rewarded objects support multiple load/show cycles. One successful load provides one fullscreen impression, so reload the same object after the previous ad closes:
create once
→ load
→ wait for EVENT_AD_LOADED
→ show once
→ wait for EVENT_AD_CLOSED
→ load the same handle again
→ show again
Do not recreate the object for every impression. Call destroy_*_ad() only
when permanently finished with that object; destruction invalidates its handle
and a later use requires creating a new object.
Banner objects are also created once. After loading, the native banner auto-refreshes. The same handle can be hidden and shown repeatedly without recreating or reloading the view.
The repository example automatically creates its default interstitial,
rewarded, and banner objects after EVENT_INIT_SUCCEEDED. Its Create
buttons are mainly there to test creating another object after the corresponding
Destroy button has invalidated the previous handle.
See Unity’s reusable-object guidance for interstitial and rewarded ads.
The Test Suite is a separate initialization path. Before initialization, set
is_test_suite metadata to enable. On EVENT_INIT_SUCCEEDED, call
launch_test_suite() immediately; do not create ad objects or invoke other
LevelPlay SDK operations first. Because LevelPlay initializes once per app
process, restart the app before switching between the Test Suite and the normal
ad flow. The example exposes these as separate Initialize and Test suite
buttons.
Loading and showing are separate operations:
levelplay.load_interstitial_ad(interstitial)
-- Later, after EVENT_AD_LOADED:
local placement = "Default"
if levelplay.is_interstitial_ad_ready(interstitial)
and not levelplay.is_interstitial_placement_capped(placement) then
levelplay.show_interstitial_ad(interstitial, placement)
end
After EVENT_AD_CLOSED, call load_interstitial_ad(interstitial) again to
prepare the next impression. The same object and handle are reused.
Call destroy_interstitial_ad() when the object is no longer needed. Destroy
invalidates the handle and intentionally stops its future callbacks, so do not
destroy while loading or displaying when you still require the terminal event.
Use is_interstitial_placement_capped(placement) before presenting a
placement-driven entry point.
levelplay.load_rewarded_ad(rewarded)
local placement = "Default"
if levelplay.is_rewarded_ad_ready(rewarded)
and not levelplay.is_rewarded_placement_capped(placement) then
levelplay.show_rewarded_ad(rewarded, placement)
end
After the reward and close callbacks have completed, call
load_rewarded_ad(rewarded) again to prepare the next impression. Do not
recreate the rewarded object after every show.
Grant the reward when the callback receives MSG_REWARDED with
EVENT_AD_REWARDED. The message includes the ad handle and reward data when
supplied by the SDK. get_reward(handle, placement) can inspect the dashboard
reward before showing. Never destroy a displayed rewarded object before its
reward and close callbacks; destruction invalidates the handle and stops future
callbacks.
local banner = levelplay.create_banner_ad(BANNER_AD_UNIT_ID, {
size = levelplay.BANNER_SIZE_ADAPTIVE,
position = levelplay.BANNER_POSITION_BOTTOM,
placement = "DefaultBanner",
respect_safe_area = true,
})
levelplay.load_banner_ad(banner)
The native banner view is visible by default, so loading also displays it.
hide_banner_ad() preserves the banner for later display and
show_banner_ad() reveals it again. pause_banner_auto_refresh() and
resume_banner_auto_refresh() control refresh without changing visibility.
destroy_banner_ad() removes the native view and invalidates the handle.
The callback signature is:
function(self, message_id, message)
message_id identifies initialization, interstitial, rewarded, banner, or tracking. Every asynchronous SDK message contains message.event. Events emitted by an existing ad object also contain message.handle, which lets one callback route events from multiple ad-unit objects; a validation error raised before creation succeeds has no handle.
Failure events include error_code and error_message; iOS can additionally supply error_domain. Successful ad events can include ad-unit, placement, network instance, revenue, auction, creative, and the flat size fields ad_width, ad_height, ad_size_description, and ad_size_is_adaptive. A rewarded event uses reward_name and reward_amount. iOS can additionally supply conversion_value. Check the event constant before reading optional fields.
See example/callback.lua for complete routing and example/main.gui_script for the full lifecycle.
This extension has no compatibility layer for the retired singleton API. Migrate to per-ad-unit objects and explicit loads. The official guides explain the corresponding SDK changes:
Source code and releases are available at github.com/defold/extension-levelplay.