Getting Started
Everything you need to render your first widget — installation, configuration, the two ways to load widgets, and authentication.
Installation
The widget library is available in three integration approaches. Pick the one that fits your page.
| Approach | Best for |
|---|---|
| Auto-Update Loader ✅ Recommended | Always getting the latest version automatically, optimal performance |
| Direct IIFE Build | Broad browser support, multiple widgets per page |
| Direct ESM Build | Performance optimization, 1–2 widgets per page |
Approach 1: Auto-Update with Loader (Recommended)
Best for: Always getting the latest version automatically, optimal performance.
The loader script automatically:
- Fetches the latest widget version via
manifest.json - Detects browser capabilities and loads the best build (ESM or IIFE)
- Provides a fallback if the manifest fetch fails
<!-- Step 1: Load the tiny loader script -->
<script src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@latest/fu-widgets-loader.js"></script>
<!-- Step 2: Use the loader -->
<script>
FuWidgetLoader.load({
onReady: function (FuWidget, manifest) {
console.log("Widget ready! Version:", manifest.version);
// Load your widget
FuWidget.loadWidget({
clientId: "your-client-id",
apiKey: "your-api-key",
configId: "your-config-id",
contents: [
{
id: "your-content-id",
type: "classic-quiz",
container: "widget-container",
}
]
});
},
onError: function (error) {
console.error("Failed to load widget:", error);
},
});
</script>
<div id="widget-container"></div>Pros:
- ✅ Always gets the latest version automatically (CDN cache is purged on publish)
- ✅ Perfect cache busting (hashed filenames on widgets)
- ✅ Smart build selection (ESM vs IIFE)
- ✅ Fallback to known version if manifest fails
Approach 2: Direct Load with Versioned URL (Simple)
Best for: Full control over version, simple integration.
Option 1: IIFE Build (Traditional)
Best for: Backward compatibility, traditional script loading, works everywhere.
<script src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.iife.js"></script>Pros:
- ✅ Works in all browsers (no ES6 module support needed)
- ✅ Simple script tag loading
- ✅ Backward compatible with existing implementations
Cons:
- ❌ Larger initial bundle size
- ❌ All widgets loaded upfront
Option 2: ESM Build With Code Splitting
Best for: Performance optimization, single-widget pages.
<script type="module" src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js"></script>Pros:
- ✅ Code splitting — widgets loaded on-demand
- ✅ 80% smaller initial load
- ✅ Better caching (shared chunks cached separately)
- ✅ Faster page load times
Cons:
- ❌ Requires a modern browser with ES6 module support
Important: Replace<version>with the latest version number. You can check the latest version on the npm registry page for fansunited-widgets-cdn. The current example uses version0.0.95, but newer versions may be available.
Which one should I use?
- Use IIFE if you need broad browser support or have multiple widgets on the same page.
- Use ESM if you have 1–2 widgets per page and want optimal performance.
Basic Configuration
Initialize the widget library with FuWidget.init(). Only language and fansUnited are required — everything else is optional and sets global defaults that individual widgets can override.
Most options below are documented in full on the Features and Widgets pages. This section is the single-glance reference for whatinit()accepts.
FuWidget.init({
// Required: Widget UI language
language: "en", // "bg", "en", "ro", "pt", "sr", "fr", "de", "it", "fr-be", "pl", "pt-br", "sk", "el"
// Required: Fans United API configuration
fansUnited: {
apiKey: "your-fu-api-key",
clientId: "your-client-id",
environment: "prod", // "dev", "prod", "staging", "watg", "yolo", "cska"
lang: "en", // Content language: "bg", "en", "ro", "el", "sk", "pt", "sr", "hu", "sv", "es", "fr", "nl", "de", "it"
idSchema: "native", // "native", "enetpulse", "sportradar", "sportal365", "api_football"
errorHandlingMode: "standard", // "default", "standard"
// Authentication options (choose one if needed):
tokenString: "your-auth-token", // Optional: Direct token authentication
tokenCookieName: "your-token-cookie-name", // Optional: Cookie-based authentication
},
// Optional: Firebase configuration (required if not using tokenString or tokenCookieName)
firebase: {
apiKey: "your-firebase-api-key",
authDomain: "your-app.firebaseapp.com",
projectId: "your-project-id",
appId: "your-app-id",
measurementId: "your-measurement-id", // Optional: Google Analytics measurement ID
},
// Optional: Theme configuration
theme: {
mode: "light", // "light" or "dark"
primaryColor: {
50: "#fef1f2",
100: "#fee4e6",
200: "#fecdd3",
300: "#fda4af",
400: "#fb7185",
500: "#f43f5e",
600: "#e11d48",
700: "#be123c",
800: "#9f1239",
900: "#881337",
},
},
// Optional: Lead collection configuration
leads: {
defaultFields: ["fullName", "email"], // Available: "fullName", "firstName", "lastName", "email", "gender", "country", "phoneCountryCode", "phoneNumber"
position: "after", // "before" or "after"
campaignId: "your-campaign-id",
campaignName: "Your Campaign Name",
phoneCountryCode: "+1", // Default phone country code
syncWithProfile: false, // Sync anonymous profiles to database after lead submission
},
// Optional: Default layout template for widgets
layoutTemplate: "STANDARD", // "STANDARD", "SPLIT", "OVERLAY"
// Optional: Default options layout
optionsLayout: "column", // "twoByTwo", "row", "column"
// Optional: Default image position
imagePosition: "left", // "left" or "right"
// Optional: Show answer explanations in classic and personality quiz widgets
showAnswerExplanations: true,
// Optional: Show prediction details in match quiz and event widget
showPredictionDetails: false,
// Optional: Show countdown timer in match quiz and event game widget
showCountdown: false,
// Optional: Show team labels in match quiz widget
showTeamLabels: true,
// Optional: Show points in match quiz and event widget
showPoints: false,
// Optional: Default variant for Chance Game widget
chanceGameVariant: "prize_wheel", // "prize_wheel", "penalty_shootout", "pick_one_of_x"
// Optional: Advanced theming options (see Features → Theming)
themeOptions: {
// Custom theme options for polls and quizzes
},
// Optional: Default placeholder image URL
defaultImagePlaceholderUrl: "https://example.com/placeholder.jpg",
// Optional: List widget configuration
list: {
classicQuizUrl: "/quiz/{CONTENT_ID}",
personalityQuizUrl: "/personality-quiz/{CONTENT_ID}",
eitherOrUrl: "/either-or/{CONTENT_ID}",
pollUrl: "/poll/{CONTENT_ID}",
matchQuizUrl: "/match-quiz/{CONTENT_ID}",
},
// Optional: URL patterns for content types
urls: {
classicQuizUrl: "/quiz/{CONTENT_ID}",
personalityQuizUrl: "/personality-quiz/{CONTENT_ID}",
eitherOrUrl: "/either-or/{CONTENT_ID}",
pollUrl: "/poll/{CONTENT_ID}",
matchQuizUrl: "/match-quiz/{CONTENT_ID}",
},
// Optional: Sign-in CTA configuration
signInCTA: {
defaultLabel: "Sign in to participate",
onClick: () => {
window.location.href = "/signin";
},
url: "https://yourdomain.com/signin", // Optional: Direct URL for the CTA
target: "_self", // Optional: Link target (_self, _blank)
},
// Optional: Additional CTA configuration
additionalCTA: {
defaultLabel: "Learn More",
onClick: () => {
console.log("Additional CTA clicked");
},
url: "https://yourdomain.com/info", // Optional: Direct URL for the CTA
target: "_blank", // Optional: Link target (_self, _blank)
},
// Optional: Rules display configuration
rulesDisplay: {
type: "modal", // "modal" or "link"
url: "https://example.com/rules", // Required when type is "link"
target: "_blank", // Optional: Link target (_self, _blank, _parent, _top)
},
// Optional: Player of the Match configuration
playerOfTheMatch: {
authRequirement: "REGISTERED", // "REGISTERED" or null - Require authentication for POTM voting
},
// Optional: Discussion widget configuration
discussion: {
defaultSort: "LATEST", // "OLDEST", "LATEST", "INTERACTED", "POPULAR"
postsPerPage: 10,
showReactions: true,
showReplies: true,
showReportPost: true,
showModeratedPosts: false,
infiniteScroll: false,
allowAnonymous: false,
},
// Optional: Either/Or widget configuration
eitherOr: {
showStandings: true
},
// Optional: Leaderboard widget configuration
leaderboard: {
pageSize: 10,
},
// Optional: Predictor widget configuration
predictor: {
tabs: ["play", "leaderboard", "private-leagues", "rules", "prizes"], // Optional: tabs to enable (default: all tabs)
matchCardBgImageUrl: "https://example.com/stadium-bg.jpg", // Optional: background image for match prediction cards
consents: [], // Optional: consent definitions required before predicting (see Widgets → Predictor)
betslip: {
trigger: "predictions-only", // "predictions-only" or "odds-only" — when betslip becomes available in Predictor
position: "bottom-right", // Betslip position (see Widgets → Betslip for all options)
currency: "USD",
},
},
// Optional: Betslip widget configuration
betslip: {
position: "bottom-right", // Where the widget appears on the page (see Widgets → Betslip)
maxSelections: 10, // Maximum number of selections allowed
stakePresets: [5, 10, 20, 50], // Quick-stake preset buttons shown to the user
oddsPollingInterval: 5000, // How often to refresh odds, in milliseconds
currency: "USD", // Currency label shown for stake and potential winnings
ctaUrlTemplate: "https://bookmaker.com/betslip?s={SELECTIONS}", // URL for the place-bet CTA button
brandingLogoUrl: "https://example.com/brand-logo.png", // Bookmaker or sponsor logo
},
});Widget Loading Approaches
There are two ways to load and initialize widgets.
| Approach | How it works | Best for |
|---|---|---|
| Init + Data Attributes | Initialize once, then add data-component elements | Multiple widgets, static HTML, SSR |
loadWidget() API | Fetch config from the API and render dynamically | Single dynamic widget, SPAs |
Approach 1: Traditional Init + Data Attributes
Initialize the widget library once, then add widget elements with data attributes to your HTML.
Step 1: Load and initialize the widget library
<script src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.iife.js"></script> <!-- or fu-widgets.es.js for ESM -->
<script>
FuWidget.init({
fansUnited: {
apiKey: "your-api-key",
clientId: "your-client-id",
environment: "prod",
lang: "en",
},
firebase: {
apiKey: "your-firebase-api-key",
authDomain: "your-app.firebaseapp.com",
projectId: "your-project-id",
appId: "your-app-id",
},
language: "en",
});
</script>Step 2: Add widget elements with data attributes
<div data-component="fu-widget" data-content-type="classic-quiz" data-content-id="your-quiz-id"></div>
<div data-component="fu-widget" data-content-type="poll" data-content-id="your-poll-id"></div>Best for:
- Multiple widgets on the same page
- Static HTML pages
- Server-side rendered content
Approach 2: API Config Fetching with loadWidget()
This approach fetches the widget configuration from the API and loads a single widget dynamically. No need to call FuWidget.init() first.
IIFE Version:
<script src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.iife.js"></script>
<script>
// Load widget with API config fetching
FuWidget.loadWidget({
// Required: API credentials
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
// Required: Widgets content
contents: [
{
id: "your-content-id",
type: "classic-quiz", // or "poll", "personality-quiz", etc.
container: "widget-container", // ID or HTMLElement
}
],
// Optional: Config overrides
configOverrides: {
language: "en",
imagePosition: "left",
},
});
</script>
<!-- Container where widget will be rendered -->
<div id="widget-container"></div>ESM Version (with Code Splitting):
<script type="module" src="https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js">
FuWidget.loadWidget({
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
contents: [
{
id: "your-content-id",
type: "classic-quiz",
container: "widget-container",
}
],
configOverrides: {
language: "en",
},
});
</script>
<div id="widget-container"></div>OR using an import:
<script type="module">
import FuWidget from "https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js";
FuWidget.loadWidget({
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
contents: [
{
id: "your-content-id",
type: "classic-quiz",
container: "widget-container",
},
],
configOverrides: {
language: "en",
},
});
</script>
<div id="widget-container"></div>Multiple Widgets with Same Config:
<script type="module">
import FuWidget from "https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js";
FuWidget.loadWidget({
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
contents: [
{
id: "quiz-1",
type: "classic-quiz",
container: "widget-1",
},
{
id: "quiz-2",
type: "personality-quiz",
container: "widget-2",
},
],
configOverrides: {
language: "en",
},
});
</script>
<div id="widget-1"></div>
<div id="widget-2"></div>Without Container (Inserts before script tag):
<!-- Widget will be inserted here, before the script tag -->
<script type="module">
import FuWidget from "https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js";
FuWidget.loadWidget({
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
contents: [
{
id: "your-content-id",
type: "classic-quiz",
// No container - widget is inserted before the script tag
},
],
configOverrides: {
language: "en",
},
});
</script>Leaderboard Widget Example:
<script type="module">
import FuWidget from "https://cdn.jsdelivr.net/npm/fansunited-widgets-cdn@<version>/fu-widgets.es.js";
FuWidget.loadWidget({
apiKey: "your-api-key",
clientId: "your-client-id",
configId: "your-config-id",
contents: [
{
type: "leaderboard",
entities: [
{ id: "your-match-quiz-id", type: "MATCH_QUIZ" },
{ id: "your-top-x-id", type: "TOP_X" },
],
container: "leaderboard-container",
},
],
});
</script>
<div id="leaderboard-container"></div>loadWidget() Options:
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Fans United API key for Client API authentication |
clientId | string | Yes | Your Fans United client ID |
configId | string | Yes | Configuration ID to fetch from the API |
contents | WidgetContent[] | Yes | Array of widgets to load (see below) |
configOverrides | Partial<Config> | No | Override specific configuration options |
WidgetContent Object:
| Property | Type | Required | Description |
|---|---|---|---|
id | string | No* | Content ID for the widget. Required for all widget types except leaderboard |
type | string | Yes | Widget type (e.g., "classic-quiz", "poll", "leaderboard") |
container | string | HTMLElement | No | Container element ID or HTMLElement where widget will be rendered |
entities | LeaderboardEntity[] | No* | Array of leaderboard entities. Required (instead of id) when type is "leaderboard" |
Note: Eitheridorentitiesmust be provided — passing neither will throw an error.
Container Behavior:
- If
containeris provided: the widget element is created and appended to the specified container. - If
containeris omitted: the widget element is inserted before the current script tag (standard for dynamic scripts).
Authentication
The widget supports multiple authentication methods for connecting to the Fans United API. They are processed in priority order: direct token → cookie token → Firebase anonymous.
Authentication Hierarchy
- If
tokenStringis provided, use it directly. - If
tokenStringis not provided buttokenCookieNameis, check for the cookie:- If the cookie exists, use its value as the token.
- If the cookie does not exist, fall back to Firebase authentication.
- If neither
tokenStringnortokenCookieNameis provided, use Firebase authentication.
1. Direct Token Authentication
Provide a valid authentication token directly in the configuration. This is the highest priority method and will be used if provided.
FuWidget.init({
fansUnited: {
// ... other config
tokenString: "your-auth-token", // Direct authentication token
},
// ... other config
});2. Cookie-based Authentication
Use a token stored in a browser cookie. The widget looks for a cookie with the specified name and uses its value as the authentication token. This method is used if direct token authentication is not provided and the cookie exists.
FuWidget.init({
fansUnited: {
// ... other config
tokenCookieName: "your-token-cookie-name", // Name of the cookie containing the token
},
// ... other config
});
Note: When usingtokenCookieName, the widget automatically re-fetches the cookie value for each API request, ensuring it always uses the latest token value.
3. Firebase Anonymous Authentication (Default)
If neither tokenString nor a valid tokenCookieName cookie is provided, the widget falls back to Firebase anonymous authentication. This is the default method and requires valid Firebase configuration.
FuWidget.init({
fansUnited: {
// ... other config
},
firebase: {
apiKey: "your-firebase-api-key",
authDomain: "your-app.firebaseapp.com",
projectId: "your-project-id",
appId: "your-app-id",
},
// ... other config
});
See Features → Authentication Requirements forFREE/LEAD/REGISTEREDcontent gating, and Features → Sign-in & Additional CTA for customising the sign-in prompt shown to anonymous users.
