Mobile (iOS & Android)
The JS SDK targets browser iframes. On native apps you load the play shell directly into a WebView and receive lifecycle events through a JavaScript bridge.
The web SDK (Playgent.init) is iframe-based and not used on native apps. Instead, you load the play shell URL into a WKWebView (iOS) or WebView (Android) and register a JavaScript bridge. The shell detects the bridge and routes every lifecycle event to native code.
Native WebView embedding requires allow_webview: true on the player. Toggle it in Hub → Configuration → Players → (your player) → Allow embedding in iOS / Android WebViews. Without it the shell refuses to load (origin allowlists don't apply to WebView traffic — allow_webview is the gate).
Shell URL
https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=ios
Everything is configured through query parameters — there is no init() call.
Required
playerrequiredPlayer ID (plyr_…). Bundles theme, features, CTA, and analytics stream.
gamerequiredGame ID, e.g. sudoku, trivia, wordsearch.
bridgerequiredTells the shell to deliver lifecycle events through the matching JS bridge instead of window.parent.postMessage. Omit on web embeds.
Content & mode
modeHow content is fed. See Content modes.
contentPublication ID (pub_…) for pinned, or channel ID (cha_…) for daily / random. Required for content-driven games; omitted for algorithmic ones.
langen, he, es).difficultydateYYYYMMDD. Pins daily mode to a specific date.Theme & identity
themethm_…). Overrides the player's theme.themeIdtheme for custom themes.navexternalUserIdexternalUsernameEvery option from the JS SDK init reference has a query-parameter equivalent — disableHints → hints=false, disableShare → share=false, muted=true, hintCount=3, ctaText, ctaButtonText, ctaUrl, playButtonText, and so on. The player's Hub configuration fills in any that you don't pass.
Bridge protocol
The shell emits the same seven lifecycle events as the web SDK (reference). On native they are delivered through a platform-specific JS interface instead of postMessage:
| Platform | JS call | Payload form |
|---|---|---|
| iOS | window.webkit.messageHandlers.playgent.postMessage(payload) | Native dictionary ([String: Any]) |
| Android | window.PlaygentAndroidBridge.postMessage(json) | JSON string |
Every payload includes a type field — pg_ready, pg_start, pg_hint, pg_complete, pg_end, pg_error, plus pg_view when the shell determines visibility. Field names match the SDK event payloads.
If the shell finishes loading before the iOS message handler (or Android JS interface) is attached, the first events — notably pg_ready — won't reach native code.
iOS (WKWebView)
Register a script message handler named playgent on the WKWebViewConfiguration, then load the shell URL with bridge=ios.
import WebKit
final class PlaygentViewController: UIViewController, WKScriptMessageHandler {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "playgent")
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
let url = URL(string:
"https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=ios"
)!
webView.load(URLRequest(url: url))
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard message.name == "playgent",
let payload = message.body as? [String: Any],
let type = payload["type"] as? String else { return }
switch type {
case "pg_ready": print("ready")
case "pg_start": print("started")
case "pg_hint": print("hint used", payload["hintsUsed"] ?? "")
case "pg_complete": print("done", payload["score"] ?? "")
case "pg_end": print("CTA pressed")
case "pg_error": print("error", payload["code"] ?? "")
default: break
}
}
}
Android (WebView)
Add a JavaScript interface named PlaygentAndroidBridge before loading the URL with bridge=android.
import android.webkit.JavascriptInterface
import android.webkit.WebView
import org.json.JSONObject
class PlaygentBridge {
@JavascriptInterface
fun postMessage(json: String) {
val payload = JSONObject(json)
when (payload.optString("type")) {
"pg_ready" -> println("ready")
"pg_start" -> println("started")
"pg_hint" -> println("hint used: ${payload.optInt("hintsUsed")}")
"pg_complete" -> println("done: ${payload.optInt("score")}")
"pg_end" -> println("CTA pressed")
"pg_error" -> println("error: ${payload.optString("code")}")
}
}
}
class PlaygentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(PlaygentBridge(), "PlaygentAndroidBridge")
setContentView(webView)
webView.loadUrl(
"https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&bridge=android"
)
}
}
addJavascriptInterface injects whatever you give it into the JS context. Don't expose database handles, file I/O, or anything that grants the WebView access to your app's internals — keep the bridge surface to the single postMessage method shown above.
Driving the player
The bridge is one-way (shell → native). Methods from the JS SDK — player.play(), player.restart(), player.hint(), player.load(opts), player.setIdentity(...), player.share(opts) — are not exposed over the bridge. To change content, swap difficulty, or jump dates from native code, reload the WebView with updated query parameters:
let url = URL(string:
"https://static.playgent.com/play/v2/?player=plyr_acme&game=sudoku&mode=daily&date=20260601&bridge=ios"
)!
webView.load(URLRequest(url: url))
Going further
The Native WebView guide walks through the full integration — process pools, network-cache gotchas, and how allow_webview interacts with the origin allowlist when the same player is also embedded on web.