Yes2SDK for Defold
A single SDK for your Defold HTML5 game. Integrate once against Yes2SDK, submit through the Yes2Games Dashboard, and the Yes2Games team handles the rest.
Requirements
- Defold 1.6 or newer
- HTML5 build target (the extension is HTML5-only — non-HTML5 platforms get a warn-and-no-op stub)
Installation
Via Git Dependency (recommended)
In your game.project, add:
[project]
dependencies#0 = https://github.com/yes2games/yes2sdk-defold/archive/refs/tags/v1.5.5.zip
Then in Defold Editor: Project > Fetch Libraries.
Pinning the URL with
tags/v1.5.5keeps the resolved hash stable. Bump the tag when a newer release ships. Don't use/refs/heads/main.zip— branch archives are served less reliably by GitHub andFetch Librariesfails intermittently.
Via Local Copy
- Download the latest release from Releases
- Copy the
yes2sdk/folder into your project root:your-game/ ├── game.project ├── yes2sdk/ ← copy here │ ├── ext.manifest │ ├── include/ │ ├── src/ │ ├── lib/web/ │ └── yes2sdk.lua └── main/ - Close and reopen Defold Editor.
Quick Start
This is the minimum integration your game must have. The lifecycle has three distinct stages — don't chain them together:
App launch → yes2sdk.initialize (SDK ready)
Splash + loading → yes2sdk.set_loading_progress(0..100) as assets load
Game playable → yes2sdk.start_game (scene ready, accepting input)
local yes2sdk = require "yes2sdk.yes2sdk"
function init(self)
-- Stage 1 — at app launch, initialize the SDK.
yes2sdk.initialize(function(self, success, error)
if not success then
print("[Game] SDK init failed: " .. tostring(error))
return
end
print("[Game] SDK ready")
-- Stage 2 — load your assets and report progress (0..100).
-- The Defold engine template reports DOWNLOAD progress automatically.
-- Use set_loading_progress only for game-specific loading after the
-- engine starts (LiveUpdate, procedural generation, asset streaming).
-- Stage 3 — splash done, scene loaded, game is playable.
yes2sdk.start_game(function(self, success, error)
if success then
yes2sdk.session_gameplay_start()
end
end)
end)
end
function final(self)
yes2sdk.session_gameplay_stop()
end
Don't call
start_gamedirectly inside theinitializesuccess handler if you have a splash/loading phase. The platform's loading bar treatsstart_gameas "the game is playable now". For games with no post-engine loading phase, calling them back-to-back is fine (Defold's engine template handles the initial download progress for you).
Without this flow your game won't be accepted for review.
Core API
Implement everything in this section. Together these cover what Yes2Games needs to validate and monetize your game.
Lifecycle (required)
yes2sdk.initialize(function(self, success, error) ... end) -- once at startup
yes2sdk.set_loading_progress(progress) -- 0..100 as assets load
yes2sdk.start_game(function(self, success, error) ... end) -- when game is playable
Important: these three calls fire in three distinct stages.
initializeis at app launch.set_loading_progressis updated as your assets load.start_gameruns only when the game is actually playable (splash gone, scene loaded, accepting input).
Ads (required)
Interstitial ads run at natural break points. Rewarded ads run only when the player opts in.
Always wrap ad calls in
session_gameplay_stop()/session_gameplay_start(). Platforms count "active gameplay seconds" for monetization — leaving gameplay running during an ad inflates those numbers and is grounds for rejection.
function show_interstitial(self)
yes2sdk.session_gameplay_stop()
yes2sdk.ads_show_interstitial("level-end",
function(self) end, -- before_ad: pause
function(self) yes2sdk.session_gameplay_start() end, -- after_ad: resume
function(self) yes2sdk.session_gameplay_start() end -- no_fill: resume immediately
)
end
function show_rewarded(self)
yes2sdk.session_gameplay_stop()
yes2sdk.ads_show_rewarded("extra-life",
function(self) end, -- before_ad: pause
function(self) yes2sdk.session_gameplay_start() end, -- after_ad: resume
function(self) yes2sdk.session_gameplay_start() end, -- ad_dismissed: no reward
function(self) grant_extra_life() end, -- ad_viewed: GRANT REWARD
function(self) yes2sdk.session_gameplay_start() end -- no_fill: resume
)
end
Rewarded ad firing order
The callbacks fire in this order. Pay attention — getting it wrong silently breaks reward logic:
before_ad → pause game (always)
(ad shown)
ad_viewed → grant reward (ONLY fires if the player watched the full ad)
— or —
ad_dismissed → no reward (fires if the player skipped/closed early)
— or —
no_fill → no ad available (fires if the platform couldn't deliver)
after_ad → resume game (always — fires after the result)
⚠️ Do NOT grant rewards in
after_ad.after_adfires for completion, dismissal, and no-fill alike — granting rewards there gives them away on skip. Always grant inad_viewed.
Concurrent ad guard + readiness
yes2sdk.ads_is_ad_showing()— returnstruewhile aads_show_interstitialorads_show_rewardedis in flight (between the call andafter_ad/no_fill). Callingads_show_*again while one is already showing is rejected immediately andno_fillfires for the rejected call.yes2sdk.ads_is_rewarded_ad_available()— best-effort check whether a rewarded ad appears available right now. Most platforms don't expose explicit readiness, so this returnstruewhile the platform's ad module is loaded; the actualads_show_rewardedcall can still no-fill. Use it as a hint, not a guarantee.
local can_show_reward = not yes2sdk.ads_is_ad_showing()
and yes2sdk.ads_is_rewarded_ad_available()
gui.set_enabled(reward_button, can_show_reward)
Session / Gameplay (required)
Tells Yes2Games when an active round begins and ends. Also call session_gameplay_stop() before any ad and session_gameplay_start() after.
yes2sdk.session_gameplay_start()
yes2sdk.session_gameplay_stop()
local locale = yes2sdk.session_get_locale() -- e.g. "en", "ja", "ru"
analytics_log_level_start/_endcan also trigger gameplay start/stop on some platforms — use either pair, but don't call both.
Data (required)
Key-value storage. Persists across sessions automatically.
yes2sdk.data_set_int("highScore", 9999)
yes2sdk.data_set_string("playerName", "Alice")
yes2sdk.data_set_float("volume", 0.75)
local score = yes2sdk.data_get_int("highScore", 0)
local name = yes2sdk.data_get_string("playerName", "Guest")
local volume = yes2sdk.data_get_float("volume", 1.0)
if yes2sdk.data_has_key("highScore") then
yes2sdk.data_delete_key("highScore")
end
Analytics (recommended)
yes2sdk.analytics_log_level_start("level-1")
yes2sdk.analytics_log_level_end("level-1", 1500, true)
-- Time-based games (racing, time-attack) can include duration:
yes2sdk.analytics_log_level_end("level-1", 1500, true, 87.3)
yes2sdk.analytics_log_score(9999)
yes2sdk.analytics_log_tutorial_start()
yes2sdk.analytics_log_tutorial_end()
yes2sdk.analytics_log_game_choice("character", "wizard")
-- Custom event with optional params (params as a JSON string):
yes2sdk.analytics_log_event("boss_defeated")
yes2sdk.analytics_log_event("boss_defeated", json.encode({ level = 3, time = 42.5 }))
Custom events are delivered to Yandex Metrica as
reachGoal(event_name, params)on builds with a Metrica counter configured; on other platforms they are logged through the SDK's analytics pipeline.
Optional APIs
These modules add extra player-facing features. They are not guaranteed to be available at runtime — guard with a support check and handle the unsupported case gracefully. Don't make your core gameplay depend on them.
Support checks available: ads_is_interstitial_supported(), ads_is_rewarded_supported(), auth_is_supported(), player_is_data_supported(), friends_is_supported(), banners_is_supported(), score_is_supported(), leaderboard_is_supported(), stats_is_supported(), config_is_supported(), review_is_supported(), iap_is_supported().
if yes2sdk.ads_is_rewarded_supported() then
-- show a "Watch ad for reward" button
end
Auth
if yes2sdk.auth_is_authenticated() then
-- already signed in
else
yes2sdk.auth_sign_in(function(self, success, error)
if success then print("Signed in") end
end)
end
Friends
if yes2sdk.friends_is_supported() then
yes2sdk.friends_list_friends(0, 10, function(self, success, page_json)
if success then
local page = json.decode(page_json)
for _, friend in ipairs(page.friends) do
print(friend.username, friend.id)
end
end
end)
end
Banners
if yes2sdk.banners_is_supported() then
yes2sdk.banners_show("sidebar-left", "300x250")
yes2sdk.banners_hide("sidebar-left")
yes2sdk.banners_hide_all()
end
Score
if yes2sdk.score_is_supported() then
yes2sdk.score_add(9999)
yes2sdk.score_submit("encrypted-score-string")
end
Player Data (cloud)
yes2sdk.player_get_data(json.encode({"level"}), function(self, success, data_json)
if success then
local data = json.decode(data_json)
print(data.level)
end
end)
yes2sdk.player_set_data(json.encode({level = 5}), function(self, success, error) end)
Game Extras
yes2sdk.game_happy_time() -- positive-moment signal
yes2sdk.game_copy_to_clipboard("https://...")
yes2sdk.game_invite_link(json.encode({roomId = "abc"}), function(self, success, url) end)
local settings_json = yes2sdk.game_get_settings()
game_happy_time()signals to the platform that the player just hit a positive moment — level cleared, achievement unlocked, boss defeated. Some platforms (notably CrazyGames) use this to time monetization prompts so they don't interrupt frustrating moments. Call it sparingly, only on genuine highs.
Integration Checklist
Your build is ready for review when:
-
initializeis called at startup -
set_loading_progressis called as assets load (or you rely on the engine template's automatic download progress) -
start_gameis called when the game is playable - Interstitial ads run at natural break points
- Rewarded ads grant reward only in
ad_viewed -
session_gameplay_stop()is called before every ad;session_gameplay_start()after - Gameplay resumes in
after_adANDno_fill -
data_*functions are used for persistent player data
The QA Inspector in the Yes2Games Dashboard validates all of this automatically.
Branded Loading Screen (Optional)
Yes2SDK includes a custom HTML5 template with an animated Yes2Games loading screen. To use it, add this to your game.project:
[html5]
custom_html_shell = /yes2sdk/html5/engine_template.html
This replaces the default Defold loading bar with:
- Animated Yes2Games logo with breathing glow effect
- Slim progress bar with shimmer animation
- Smooth fade-out when loading completes
The template automatically reports Defold's download progress to the loading screen — no Lua code needed for the initial load. set_loading_progress(progress) is for game-specific loading that happens after the engine starts (LiveUpdate, procedural generation, etc.).
Note: Calling
set_loading_progress()afterstart_game()has no visible effect — the loading screen is already dismissed by then.
Build & Submit
- Build: Project > Bundle > HTML5 Application > Create Bundle
- Zip the output folder (the folder containing
index.html) - Upload the zip to the Yes2Games Dashboard
- The dashboard handles SDK injection, platform bundling, and walks you through the QA Inspector and review request.
Always use Project > Bundle for HTML5, not Project > Build. The regular Build command doesn't compile native extensions through the build cloud.
Editor Testing
The native extension is HTML5-only. In the Defold editor, all yes2sdk.* calls run against a no-op stub that prints a one-time warning and returns sensible defaults. To exercise real SDK behavior, build for HTML5 and run via Bundle > HTML5 Application > Create Bundle, or use the QA Inspector in the Yes2Games Dashboard for richer simulation (forced errors, specific locales, ad failure modes).
Running alongside other SDKs
Real games often ship with multiple platform SDKs in the same build (Yes2SDK + Poki + Yandex + Playgama, etc.). A few ground rules to keep them from stepping on each other:
- Init order. Initialize Yes2SDK first. Yes2SDK figures out which actual platform is hosting the game and routes through it — initializing your own platform SDK directly first can race with Yes2SDK's detection.
- One owner for pause / resume. Pick one SDK to drive your game's pause state. If both Yes2SDK and another SDK call resume/pause via callbacks, you'll get oscillation. Recommended: handle pause/resume only via Yes2SDK's
ads_show_*before_ad/after_adcallbacks. - One owner for ads. Don't call ads via two SDKs in the same session — the platform almost always rejects the second call. Pick the SDK that targets the platform you're actually hosted on.
Troubleshooting
yes2sdk is nil at runtime
The native extension wasn't compiled. You used Project > Build instead of Project > Bundle > HTML5 Application > Create Bundle. Bundle compiles extensions through Defold's build cloud.
"Couldn't install dependencies" when fetching the library
Use a tagged release URL, not a branch archive. GitHub serves tagged archives more reliably:
# Good — tagged release
dependencies#0 = https://github.com/yes2games/yes2sdk-defold/archive/refs/tags/v1.5.5.zip
# Bad — branch archive (intermittent failures)
dependencies#0 = https://github.com/yes2games/yes2sdk-defold/archive/refs/heads/main.zip
If it still fails, fall back to the local-copy install (see Installation > Via Local Copy).
excluded_content.zip 404 in console
This is Defold's LiveUpdate feature looking for excluded content. If your project does not use Exclude Resources (the default), this 404 is harmless.
If your project does use Exclude Resources (game.project → liveupdate.enabled), this 404 means the excluded content was not uploaded alongside your game bundle. Include excluded_content.zip in your upload, or disable Exclude Resources and re-bundle.
Game plays but no SDK events in the QA Inspector
- Check the browser console for
[Yes2SDK]log messages. - If no logs appear, the extension wasn't included — see "extension nil" above.
- Make sure
yes2sdk.initialize()is called early ininit()of your main script.
License
MIT — see LICENSE.