Samsung Tizen setup for the SofAds SDK
Every Tizen app that uses SofAds needs webapis.js loaded before the SDK and the starter config.xml lines below: one access rule, one navigation rule and four privileges. Apps that may get Google demand add a second, clearly marked block. Hosted apps also need a small packaged start page. Below are complete files you can copy.
Checklist
- Your Tizen application ID is on the listing in the dashboard.
<script src="$WEBAPIS/webapis/webapis.js">comes beforejs/sofads.min.jsinindex.html.- The starter config.xml lines are in your
config.xml:<access origin="*" subdomains="true">,<tizen:allow-navigation>*</tizen:allow-navigation>and the four privileges. Put them in your first release: published TV apps are rarely updated. - If your app may ever get Google demand: the Google (PAL) block is in
config.xmltoo. - A hosted app ships the packaged start page and keeps one permanent domain.
- Your app's privacy policy has the lines from the privacy section.
The full API is in the SofAds SDK reference, and the steps for every platform are on the release checklist.
Your Tizen application ID
When you add a Samsung Tizen listing, the dashboard asks for your Tizen application ID. It is the id of <tizen:application> in your config.xml: a package ID of 10 letters or digits, a dot and a name, for example AbCd123456.SnowStrike. Tizen Studio creates it with the project, and it stays the same for the life of the app.
<tizen:application id="AbCd123456.SnowStrike" package="AbCd123456" required_version="2.3"/>The SDK reads the same ID on the TV (tizen.application.getCurrentApplication().appInfo.id), so there is nothing to pass, and sends it with every request. A listing with an ID only accepts traffic from that app, so copy it exactly, capitals included. The store URL can wait until the app is live in the store.
index.html
The order matters. $WEBAPIS is a placeholder that the Tizen runtime fills in when it loads your page, so the tag has to be in your HTML; a script can't add it later. The SDK reads TIFA, Limit Ad Tracking and the model through webapis, so it must exist when the SDK starts.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My TV game</title>
<!-- 1. Samsung Web APIs. The Tizen runtime resolves $WEBAPIS, so this tag
must be in your HTML; the SofAds SDK cannot load it for you. -->
<script src="$WEBAPIS/webapis/webapis.js"></script>
<!-- 2. The SofAds SDK, bundled inside the .wgt package. -->
<script src="js/sofads.min.js"></script>
<!-- 3. Your app. -->
<script src="js/main.js" defer></script>
</head>
<body>
<canvas id="game" width="1920" height="1080"></canvas>
</body>
</html>js/main.js starts the SDK. The SDK reference has every option:
// js/main.js
SofAds.init({
appId: "sof_YOUR_APP_ID",
onPause: () => game.pause(), // stop your render loop before an ad plays
onResume: () => game.resume(),
});You never add Google's pal_ctv.js yourself. When Google demand is enabled for your app on Tizen, the SDK loads it from Google's own URL after webapis.js, which is how Google requires it to be loaded.
config.xml
A complete config.xml with the starter lines and the Google block. Keep your own application ID, package, name and icon; the SofAds parts are between the marked comments.
<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
id="http://example.com/mytvgame" version="1.0.0" viewmodes="maximized">
<tizen:application id="AbCdE12345.MyTvGame" package="AbCdE12345" required_version="2.3"/>
<tizen:profile name="tv-samsung"/>
<content src="index.html"/>
<icon src="icon.png"/>
<name>My TV game</name>
<tizen:setting screen-orientation="landscape" context-menu="enable"
background-support="disable" encryption="disable"
install-location="auto" hwkey-event="enable"/>
<!-- ============ SofAds starter setup: every app ============ -->
<access origin="*" subdomains="true"></access>
<tizen:allow-navigation>*</tizen:allow-navigation>
<tizen:privilege name="http://tizen.org/privilege/internet"/>
<tizen:privilege name="http://developer.samsung.com/privilege/adinfo"/>
<tizen:privilege name="http://developer.samsung.com/privilege/productinfo"/>
<tizen:privilege name="http://tizen.org/privilege/tv.inputdevice"/>
<!-- ============ end of SofAds starter setup ============ -->
<!-- ============ Google demand (PAL) ============ -->
<!-- Add this block if your app may get Google demand, now or later. -->
<tizen:privilege name="http://tizen.org/privilege/system"/>
<tizen:privilege name="http://tizen.org/privilege/telephony"/>
<tizen:privilege name="http://developer.samsung.com/privilege/network.public"/>
<!-- ============ end of Google demand (PAL) ============ -->
</widget>The starter lines (every app)
| Line | Why SofAds needs it |
|---|---|
<access origin="*" subdomains="true"> | Network access to every host. See why everything. |
<tizen:allow-navigation>*</tizen:allow-navigation> | Lets your packaged page open your hosted app's URL. See why navigation. |
http://tizen.org/privilege/internet | Network access for ad requests, media and events. |
http://developer.samsung.com/privilege/adinfo | TIFA (the Tizen advertising ID) and the Limit Ad Tracking setting. |
http://developer.samsung.com/privilege/productinfo | TV model and firmware version. |
http://tizen.org/privilege/tv.inputdevice | Remote keys beyond the arrows and OK, such as BACK. |
Google demand (PAL)
Google's Programmatic Access Library (PAL) is fully supported only on Samsung Tizen TVs from 2016 onwards, and Google demand needs Google's approval. PAL is a script the SDK loads from Google's host, and Samsung asks you to get permission from your Content Manager in Seller Office before an app loads external scripts, so mention it when you submit. Add the block if your app may ever get Google demand: like the starter lines, it can't be added to apps already on TVs without a new release. The access rule above already covers Google's hosts (imasdk.googleapis.com and Google's ad servers), so the block only adds privileges.
| Privilege | Why |
|---|---|
http://tizen.org/privilege/system | Required by Google PAL. |
http://tizen.org/privilege/telephony | Required by Google PAL. |
http://developer.samsung.com/privilege/network.public | Required by Google PAL. |
Why access to every host
<access origin="*"> lets the app reach any host. SofAds asks for it because the hosts an ad needs can't be known when you publish:
- Published apps are rarely updated. A TV app often stays on TVs for years in the version you submitted, so a host list written today would block what comes later.
- Partner ads load from their own hosts. Video ads from partners (VAST) bring media and tracking URLs on domains chosen by that partner, per ad.
- Google PAL and future partners. PAL loads from
imasdk.googleapis.com, and the next partner will bring its own hosts.
SofAds itself keeps serving its own creatives from cdn.sofadsrv.com, so its own ads never depend on this rule. You don't need a separate entry for the serving domain or for any CDN.
Why allow-navigation
<tizen:allow-navigation>*</tizen:allow-navigation> lets the top-level page move to another URL. A hosted app needs exactly that: its packaged index.html opens the app's own website. Like the access rule, it has to be in the first release, because you can't add it to apps already on TVs. Tizen applies it to the main page: navigation through window.open() or a link to an outside URL is allowed or refused according to this list.
How the SDK keeps ads from navigating your app
Opening navigation for your app doesn't open it for ads. The protection lives in the SDK:
- HTML ads run in a sandboxed
<iframe>that allows scripts but notallow-top-navigationorallow-top-navigation-by-user-activation, so an ad can't replace your page (window.top.location,top.location.hrefandwindow.open(…, "_top")are all blocked). - The SDK itself never navigates the top-level page. A QR code is scanned with the viewer's phone, so a landing page never opens on the TV.
- Video ads play in the SDK's own player; their click-through URLs are never opened on the TV.
The CSP-based security mode
Tizen's documentation says that an app with <tizen:allow-navigation> runs in the CSP-based security mode, where the runtime may enforce a default Content Security Policy (default-src *; script-src 'self'; style-src 'self'; object-src 'none') unless config.xml sets one with <tizen:content-security-policy>. To stay on the safe side:
- Keep your scripts and styles in files inside your package or on your hosted site, never inline; Samsung's review asks for a policy without
unsafe-inlineandunsafe-evalanyway. - Bundle
sofads.min.jswith your app (or serve it from your hosted site), as shown above. - Test your release build on a real TV, including a test ad, before you submit. When Google demand is enabled for your app, test it on a TV too, because PAL is loaded from Google's host.
Hosted apps
A hosted app is a small .wgt whose index.html opens your app on your own website, so you can update the app without a store release.
Ask Samsung first. Samsung's hosted-applications FAQ says hosted apps are generally not accepted in the store, with rare exceptions: you need to be in a partner group and to contact your Content Manager before you build one. It also says Tizen APIs are not supported on hosted pages. That has two consequences for SofAds on the hosted part of your app:
- The SDK can't read TIFA, Limit Ad Tracking or the TV model there. It uses its own install ID and the user agent instead, as described under what happens when something is missing, so ads can't be personalized and Google demand may not fill.
- Only the packaged start page can use Tizen calls, such as closing the app on BACK.
If Samsung accepts your hosted app, three rules make it work with SofAds:
- A packaged start page that opens the hosted URL, and shows a clear offline screen with a retry button when the site can't be reached. A blank or browser error screen would fail store review and lose users.
- One permanent domain per hosted app. The browser storage, and with it the SofAds install ID, belongs to the site's origin. Moving to another domain (or from
www.to the bare domain) starts every user over: frequency caps, test devices and analytics all see new users. - A pinned SDK version on the hosted site, for example
https://sdk.sofadsrv.com/v1.0.0/sofads.min.jsor a copy on your own site, never a "latest" URL. You choose when your app moves to a new SDK; the SDK changelog says what changed.
The packaged files are index.html, js/launcher.js and css/launcher.css, plus the config.xml above. The page has no inline script or style, so it also works in the CSP-based security mode.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My TV game</title>
<link rel="stylesheet" href="css/launcher.css">
<script src="js/launcher.js" defer></script>
</head>
<body>
<main id="offline" hidden>
<h1 id="offline-title">No connection</h1>
<p id="offline-text">My TV game can't reach the internet. Check the TV's network connection and try again.</p>
<button id="retry" type="button">Try again</button>
</main>
</body>
</html>// js/launcher.js: opens the hosted app, or shows the offline screen with a retry.
(function () {
var HOSTED_URL = "https://play.yourstudio.example/"; // one permanent domain
var CHECK_URL = HOSTED_URL + "health.txt"; // any small file on that site
var TIMEOUT_MS = 8000;
// The offline screen in the TV's language (the ad frame's eight languages; English otherwise).
var TEXT = {
en: ["No connection", "My TV game can't reach the internet. Check the TV's network connection and try again.", "Try again"],
nl: ["Geen verbinding", "My TV game kan het internet niet bereiken. Controleer de netwerkverbinding van de tv en probeer het opnieuw.", "Opnieuw proberen"],
de: ["Keine Verbindung", "My TV game erreicht das Internet nicht. Prüfe die Netzwerkverbindung des Fernsehers und versuche es erneut.", "Erneut versuchen"],
fr: ["Pas de connexion", "My TV game ne parvient pas à accéder à Internet. Vérifiez la connexion réseau du téléviseur et réessayez.", "Réessayer"],
es: ["Sin conexión", "My TV game no puede conectarse a internet. Comprueba la conexión de red del televisor y vuelve a intentarlo.", "Reintentar"],
it: ["Nessuna connessione", "My TV game non riesce a connettersi a internet. Controlla la connessione di rete del televisore e riprova.", "Riprova"],
pl: ["Brak połączenia", "My TV game nie może połączyć się z internetem. Sprawdź połączenie sieciowe telewizora i spróbuj ponownie.", "Spróbuj ponownie"],
pt: ["Sem ligação", "My TV game não consegue aceder à internet. Verifique a ligação de rede da TV e tente novamente.", "Tentar novamente"]
};
var lang = String(navigator.language || "en").slice(0, 2).toLowerCase();
var t = TEXT[lang] || TEXT.en;
function showOffline() {
document.documentElement.lang = TEXT[lang] ? lang : "en";
document.getElementById("offline-title").textContent = t[0];
document.getElementById("offline-text").textContent = t[1];
var retry = document.getElementById("retry");
retry.textContent = t[2];
retry.disabled = false;
document.getElementById("offline").hidden = false;
retry.focus(); // the remote's OK key presses it
}
function open() {
var done = false;
var timer = setTimeout(function () { if (!done) { done = true; showOffline(); } }, TIMEOUT_MS);
// Check that the site answers before leaving this page: a failed navigation
// would show the TV browser's own error screen, with no way back.
// "no-cors": the hosted site needs no CORS headers. Its answer is then opaque,
// and any answer means the site can be reached; a network error rejects.
fetch(CHECK_URL + "?t=" + Date.now(), { cache: "no-store", mode: "no-cors" }).then(function (res) {
if (done) return;
done = true;
clearTimeout(timer);
if (res.ok || res.type === "opaque") location.replace(HOSTED_URL);
else showOffline();
}, function () {
if (done) return;
done = true;
clearTimeout(timer);
showOffline();
});
}
document.addEventListener("DOMContentLoaded", function () {
document.getElementById("retry").addEventListener("click", function () {
this.disabled = true;
open();
});
document.addEventListener("keydown", function (e) {
// BACK (10009) on the offline screen closes the app.
if (e.keyCode === 10009) {
try { tizen.application.getCurrentApplication().exit(); } catch (err) { /* not on a TV */ }
}
});
if (navigator.onLine === false) showOffline();
else open();
});
window.addEventListener("online", function () {
if (!document.getElementById("offline").hidden) open();
});
})();/* css/launcher.css: a 10-foot offline screen, readable from the couch. */
html, body { margin: 0; height: 100%; background: #040d1f; color: #fff; font-family: sans-serif; }
main { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; padding: 5%; box-sizing: border-box; text-align: center; }
main[hidden] { display: none; }
h1 { font-size: 64px; margin: 0 0 24px; }
p { font-size: 32px; max-width: 1200px; margin: 0 0 48px; }
button { font-size: 32px; padding: 20px 48px; border-radius: 12px; border: 4px solid transparent; background: #fff; color: #040d1f; }
button:focus { outline: none; border-color: #ffb400; }On the hosted site, load the pinned SDK and start it as in index.html. Samsung doesn't support Tizen APIs on hosted pages, so don't count on $WEBAPIS/webapis/webapis.js there: the SDK works without it, with its own install ID. Add health.txt (any short text) next to your app, or point CHECK_URL at another small file. The check uses a no-cors request, so your site needs no CORS headers for it: any answer from the site counts as reachable. Change the app name in the texts to yours.
What happens when something is missing
The SDK never throws because of a setup mistake. It falls back and tells you once:
- No
webapis.js: the SDK uses its own install ID and the user agent instead of TIFA and the model, and logs one warning:SofAds: webapis.js not loaded; include $WEBAPIS/webapis/webapis.js before the SDK. - A missing privilege: when a call is refused with a
SecurityError, the SDK falls back the same way and logs one warning naming the privilege. - Both are reported with the session, so the publisher dashboard shows an integration warning on the listing, with the line to add and a link to this page.
Ads still work in both cases, but without TIFA they can't be personalized and Google demand may not fill. More fixes are on the troubleshooting page.
Checked against Samsung, Tizen and Google docs
We checked this setup against the current documentation on October 8, 2026:
<access>: Samsung documents<access origin="<SERVER_URL>" subdomains="true"></access>for external servers, and the W3C Widget Access Request Policy that Tizen follows definesorigin="*"as access to every network resource. Google's Tizen PAL guide uses<access origin="*" subdomains="true"/>itself.<tizen:allow-navigation>: Tizen'sconfig.xmlreference lists it as the list of URL domains the app may navigate to (example:tizen.org *.tizen.org). Tizen's web runtime guide says it puts the app in the CSP-based security mode and controls navigation of the main page throughwindow.open()or links.- Privilege names:
tv.inputdevice,productinfoandnetwork.publicappear in Samsung's configuration guide,internetin Samsung's networking FAQ,adinfoin Samsung's AdInfo API reference (privilege level public), and Google's Tizen PAL guide listsinternet,system,telephony,tv.inputdevice,network.publicandproductinfowith exactly these names. - Google's list doesn't include
adinfo. SofAds needs it for TIFA, so it is in the starter lines. - Hosted apps and external scripts: Samsung's hosted-applications FAQ says hosted apps are generally not accepted (rare exceptions, partner groups only, ask your Content Manager), that Tizen APIs are not supported in hosted apps, that loading external scripts needs your Content Manager's permission, and that a content security policy may not use
unsafe-inlineorunsafe-eval.
Privacy policy lines for Tizen
Your app's privacy policy has to say that it shows ads and what data that involves. Copy these lines into it and adapt them to your app. They match the SofAds privacy policy, which is itself still a draft, so treat the wording as a template and have it reviewed for your app; it is not legal advice. Which IDs the SDK reads, and when, is on the privacy and consent page.
This app shows ads from SofAds. To choose, show and measure ads, the app sends SofAds information about the app and the TV (platform, manufacturer, model, operating system version, screen size and language), your IP address, your consent choices and an advertising identifier where your choices allow it. Where no TV advertising identifier is available or allowed, SofAds uses a random install ID stored by this app, which you can reset in the app's privacy settings. SofAds privacy policy: https://sofads.com/privacy/
On Samsung TVs, the advertising identifier is the Tizen Identifier for Advertising (TIFA), sent together with your Limit Ad Tracking setting. You can reset TIFA and switch on Limit Ad Tracking in the TV's privacy settings; with Limit Ad Tracking on, only non-personalized ads are shown.
Add when Google demand (PAL) is enabled:
On Samsung TVs, some ads come from Google. For those ads, the app loads Google's Programmatic Access Library (PAL) from Google's servers. PAL sends Google information about the TV, the app and ad playback (start, end, clicks or scans, and remote interactions such as skipping) and may store an identifier on the TV. PAL reads your consent choices and shows only limited ads without advertising consent or with Limit Ad Tracking on. How Google uses this information: https://policies.google.com/technologies/partner-sites
Next steps
Test your build before the listing goes live: until then every request gets test ads, and you can register your own TV as a test device. Google requires a skip button for skippable video; the SDK follows the skip button standard. Before you submit, go through the release checklist. For the platform's business side, see Tizen monetization.
Frequently asked questions
Why must webapis.js be loaded before the SofAds SDK?
The SDK reads TIFA, Limit Ad Tracking and the TV model through webapis when it starts. $WEBAPIS is resolved by the Tizen runtime, so only a script tag in your HTML can load it.
Why does SofAds ask for access to every host?
Published TV apps are rarely updated, and partner video ads, Google PAL and future partners load from hosts nobody can list in advance. The SDK sandboxes HTML ads and never navigates your page, so the open access rule doesn't let ads take over your app.
Do I need the Google privileges if I don't use Google demand?
Only if Google demand may be enabled for your app later. The system, telephony and network.public privileges are needed when SofAds enables Google demand (PAL), and like every config.xml line they only reach TVs with a new release.
Does my app break if a privilege is missing?
No. The SDK falls back to its own install ID, logs one warning and reports it, so the publisher dashboard shows what to fix. Ads keep working, without the platform advertising ID.
Sources
Checked on October 8, 2026. Platform rules change; when a source and this page disagree, the source wins.
- Samsung Developers: Configuring TV applications (privileges)
- Samsung Developers: Networking and connectivity FAQ (access element, internet privilege)
- Samsung Developers: Hosted applications FAQ (hosted apps, Tizen APIs, external scripts, CSP)
- Samsung Developers: AdInfo API reference
- Samsung Developers: Tizen Identifier for Advertising (TIFA)
- Tizen Docs: config.xml reference (tizen:allow-navigation, access, tizen:privilege, application ID and package ID)
- Tizen Docs: Web runtime, content security policy and the CSP-based security mode
- W3C: Widget Access Request Policy (origin="*")
- Google PAL for CTV: Get started
- Google PAL: Get started on Samsung Tizen
Keep reading
- Test ads and test devices: what your app shows before its listing is live.
- Release checklist: everything to check before you submit.
- Tizen monetization: seller tiers, TIFA, Samsung Checkout and app-ads.txt.