# Ads URL: /Ads/ ## Task Help an engineer integrate Storyteller ads using the exported React Native contracts. Distinguish optional package setup from core initialization, and current APIs from the historical v10 example. ## Metadata - Slug: `ads` - Source: `public-docs/Ads.md` - Audience: React Native SDK integrators - Platforms: iOS and Android; IMA is Android phones only - Related: `Storyteller.md`, `StorytellerListViews.md`, `StorytellerEmbeddedClipsView.md` ## Overview - Core provides CMS-managed and client-supplied ads; GAM adds Google Ad Manager and AdMob; VAST adds generic and GAM VAST full-screen creatives; IMA adds one eligible opening Android Clips pre-roll. - Install optional packages at the core package version and rebuild. Register modules/listeners before ad-bearing content. Setup functions return void and do not initialize core. - GAM/AdMob need Google Mobile Ads host app IDs in Info.plist/AndroidManifest.xml and applicable iOS SKAdNetwork entries. Native dependency versions come from the integration package. - GAM and AdMob replace only the shared Google family; generic VAST and GAM VAST replace only the VAST family. Android IMA stays independent. Disable functions remove only their respective family. Google/VAST disabling completes pending JS requests with empty results but retains consumer-owned subscriptions; Google disabling retains the registered host enricher. - GAM full-screen requests use request IDs and dynamic ad-unit results. AdMob and Google bottom banners use static setup ad units. Native enrichment hooks keep Google/bidder objects outside JavaScript. - VAST dynamic parameters merge over static keys; GAM VAST uses static setup and generated tag fields. VAST diagnostics are independent of analytics and debug logging; fields and error names vary by platform. - IMA callbacks receive current item metadata and optional next-Clip categories, without nextClip.contentId. They resolve once per request, override supplied fields, retain omitted static fields and replace supplied targeting maps. Callback failure/timeout falls back to static values. An unresolved complete tag skips pre-roll; GAM mode can still issue an unusable request if required fields are missing. - Core custom-ad events have no requestId, adIndex or nextClip in their public contract. Reply APIs complete the current pending callback and require a single listener owner. ## When To Use - Integrate CMS ads, custom creatives, GAM/AdMob, generic/GAM VAST, or Android opening IMA pre-roll. - Select request-specific GAM ad units, provide VAST parameters, enrich Google requests in native code, or configure eligible Clips placements. ## Integration Steps 1. Confirm tenant/placement eligibility and ad source with Storyteller Delivery; obtain template IDs, ad units and host app IDs as needed. 2. Install the selected optional package at the core version, complete platform setup and rebuild; install pods for iOS packages. 3. Register native GAM enrichment before setupGAMModule, and establish request listeners before presenting ad-bearing content. Initialize core separately. 4. For GAM full-screen ads, respond to getAdUnitForRequest with the unchanged requestId. For AdMob and bottom banners, supply static ad units in setup. 5. For generic VAST dynamic parameters, enable usesRequestParametersCallback and respond with the matching requestId within one second; static parameters remain on timeout. 6. For IMA, guard Android-only calls, select GAM-built or complete-tag mode, and explicitly enable preRollEnabled in the presentation. 7. For custom ads, subscribe to getAdsForList and finish the current request with completeAdRequest(ad) or failAdRequest(string); implement creative fetching in the host app. 8. Set preRollEnabled and bottomBannerEnabled explicitly in any supplied adConfiguration object. Use Android provider order/cadence/fetch mode when needed, respecting platform support. ## API Cheat Sheet - setupGAMModule({ customNativeTemplateId?, bottomBannerAdUnit?, customKvps? }) — GAM templates, static bottom-banner unit and extra targeting. Targeting is gated by Storyteller ad tracking. - setupAdMobModule({ nativeAdUnit, bannerAdUnit?, bottomBannerAdUnit?, enableBannerAdPriority?, customKvps? }) — native-first by default; banner priority is false unless enabled. - disableGoogleAdsModule() — removes GAM/AdMob and leaves other supported modules, listener ownership and native enricher registration intact. - getAdUnitForRequest(listener) — EventSubscription with { requestId, adRequest }; setAdUnitResult({ requestId, adUnitId }) resolves it. Android waits one second; iOS waits five. An empty ad unit means no selected GAM unit. - GAM/VAST AdRequestPayload — stories { placement, categories, story, adIndex } or clips { collection, clip, nextClip?, adIndex }. ItemInfo categories are readonly; current item contentId is optional, populated by Android 11.6.3+ and iOS 11.7.0+ callbacks. Android nextClip contains categories only and is omitted for empty categories; iOS can supply nextClip.contentId. - Core getAdsForList — direct { stories?: { placement, categories, story }, clips?: { collection, clip } }; current item contentId/categories, no public nextClip/adIndex/requestId. - completeAdRequest(StorytellerAd) / failAdRequest(string) — void current-pending custom-ad replies, without request correlation. - Android StorytellerSdkGamModule.registerAdRequestEnricher — prepared builder with success/no-fill/failure completion. Host owns bidder timeout; no-fill/failure continues un-enriched, cancellation ignores late completion. clearAdRequestEnricher affects subsequent setup. - iOS StorytellerGamSdk.registerAdRequestEnricher — main-actor async prepared Request mutation; finish mutation before returning, clear before subsequent setup. - setupImaGamModule / setupImaAdTagModule — Android-only static or callback setup; description/content/tag URLs are HTTPS and limited to 8192 characters. Optional positive integer load timeouts use milliseconds; omission retains IMA defaults. disableImaModule removes its internally owned callback subscription and module. - setupVASTModule({ baseUrl, requestParameters?, urlFormat?, enableDebugLogging?, usesRequestParametersCallback? }) — pathSegment by default, debug/callback flags false by default. Raw values are encoded; callback keys override static keys, and supplied values override generated bid parameters. - setupGAMVASTModule({ adUnit, descriptionUrl, contentUrl?, customParams?, tagParameters?, enableDebugLogging? }) — generated Google tag with raw custom targeting; tagParameters override generated fields. No JS request-parameter callback in this mode. - disableVASTModule() — removes generic/GAM VAST and completes pending parameter requests with empty parameters; consumer subscriptions remain. - getVASTRequestParametersForRequest / setVASTRequestParametersResult — matching requestId and optional parameters within one second; omission/empty map uses static values. - onVASTDiagnostics(listener) — requestStarted, requestCompleted, requestFailed, parseFailed, wrapperResolved, mappingFailed, mediaSelectionFailed; optional fields must be handled defensively. - StorytellerClipsAdConfiguration — supplied objects default omitted boolean flags to false. iOS applies placement flags to openCollection/openCollectionWithCategory and Embedded Clips, while rows/grids ignore them. - betweenClipsAdProviderOrder — Android-only: null/omitted preserves module order; [] disables standard provider attempts; populated arrays restrict/order recognized VAST/GAM/AdMob providers. IMA/banners are independent; iOS ignores it. - frequency / initialIndex — Android-only eligible standard cadence overrides; positive frequency, nonnegative index, with 0 opening and positive indices counting content Clips. Invalid/null inherits remote cadence. - bottomBannerFetchMode — RN 12.1.1+; Android-only justInTime default or prefetch of one next Clip; prefetch may request an unseen banner, and needs placement/unit/remote eligibility. - StorytellerAd — id, advertiserName, adFormat and trackingPixels are required; image/video asset needed, video preferred; action optional. Android ignores advertiserDescription, advertiserLogoURL, responseIdentifier and format value but still requires adFormat. ## Examples - Current GAM hooks subscribe, return matching requestId and remove the subscription. The v10 tab retains its legacy NativeEventEmitter contract. - IMA illustrates GAM-built/complete tags, callback overrides and an Android-guarded collection open with explicit preRollEnabled. - VAST shows raw static query parameters, dynamic result correlation, GAM tag setup and redacted diagnostics logging. - Custom-ad listener is an excerpt requiring host loadAdForRequest. Cadence/provider/fetch examples declare typed configuration objects. ## Pitfalls / Notes - Setup order can affect module priority because selected modules append after retained modules. An Android explicit provider order excludes unrecognized custom providers and skips unavailable providers. - Reply promptly to GAM/VAST; timeout uses an empty unit/dynamic parameter set and a late response cannot alter the requested ad. AdMob does not use the GAM listener. - IMA static targeting maps are replaced, not key-merged, by callback maps. Invalid callback URLs are discarded to allow fallback. With callback-only setup, return required mode fields. Missing complete tags skip pre-roll; missing GAM fields can still create an unusable request. - Supply raw custom parameter values; let the SDK encode them. VAST final URL limit is 2048 characters on Android and 2048 UTF-8 bytes on iOS. Path-segment mode needs a base without query/fragment. - VAST does not add an IMA-style player and cannot serve Clips bottom banners. Google banner unit configuration alone does not enable the placement. - Android adConfiguration defaults in the native SDK do not override explicit RN false values for omitted flags in a supplied object. iOS ignores provider order, cadence and fetch mode. - Custom-ad duration uses seconds; use integers for Android. Omit video duration for media duration; positive supplied duration can control iOS Story playback. Omitted image duration defaults to five seconds. - Supply nonempty custom action text for cross-platform behavior: iOS suppresses the action if text is absent/blank. inApp navigation emits StorytellerSdk.userNavigatedToApp. - The portrait video encoding profile is guidance; the bridge does not transcode or validate it. Avoid logging tags, targeting values or full diagnostics payloads. ## Canonical Reference # Ads The Storyteller SDK supports ads created in the Storyteller CMS (First Party Ads), client-supplied creatives through the core SDK, and ads from Google Ad Manager, AdMob, VAST endpoints, or Google IMA through optional integration packages. Which source of ads is used can be configured on your behalf by a member of the Storyteller Delivery Team. | Integration | Package | Platforms | | --------------------------- | --------------------------------------------------- | ------------------- | | CMS and client-supplied ads | `@getstoryteller/react-native-storyteller-sdk` | iOS and Android | | Google Ad Manager and AdMob | `@getstoryteller/react-native-storyteller-sdk-gam` | iOS and Android | | Generic VAST and GAM VAST | `@getstoryteller/react-native-storyteller-sdk-vast` | iOS and Android | | Opening Clips IMA pre-roll | `@getstoryteller/react-native-storyteller-sdk-ima` | Android phones only | Install optional packages at the same version as the core SDK, rebuild the host app, and install pods for iOS packages using the [installation guide](getting-started/index.md). Complete module setup and register request listeners during startup, before presenting content that can request ads. The setup functions return `void`; they do not initialize the core SDK or return a readiness promise. ## Storyteller First Party Ads If your tenant is configured to use Storyteller First Party Ads, which can be managed in the Storyteller CMS, then no changes to the Storyteller integration code are necessary. The Ads code is managed entirely within the Storyteller SDK. ## Storyteller GAM Ads To use Ads from Google Ad Manager in Storyteller, first reach out to your Storyteller contact and they will assist you with setting up Google Ad Manager to traffic ads to Storyteller. ### Required Platform Configuration **IMPORTANT**: Before using the Storyteller GAM Ads package, you must complete the following platform-specific configuration steps: #### iOS Configuration (Required) Add your Google Mobile Ads app ID (`GADApplicationIdentifier`) and the applicable `SKAdNetworkItems` to your host app's `Info.plist` following Google's instructions: [AdMob iOS Quick Start - Update Info.plist](https://developers.google.com/admob/ios/quick-start#update_your_infoplist) #### Android Configuration (Required) Add the Google Mobile Ads app ID metadata (`com.google.android.gms.ads.APPLICATION_ID`) to your host app's `AndroidManifest.xml` following Google's instructions: [AdMob Android Quick Start - Import Mobile Ads SDK](https://developers.google.com/admob/android/quick-start#import_the_mobile_ads_sdk) ### Package Installation and Setup Install the GAM package alongside the core SDK: ```bash yarn add @getstoryteller/react-native-storyteller-sdk-gam ``` The package supplies the native Google Mobile Ads dependency. Use the host configuration sections of Google's guides; keep the dependency versions selected by the Storyteller package. Once the platform configuration is complete, you can use the package to fetch ads from Google Ad Manager or AdMob. On Android, the GAM integration resolves Google Mobile Ads 23.4.0 from the Storyteller native ads artifact and bundles Meta mediation adapter 6.18.0.0. Keep those package-selected dependencies when configuring the host; installing an unrelated Google Mobile Ads version can change the supported combination. To initialize GAM package, call `setupGAMModule` method: ```typescript import { setupGAMModule } from '@getstoryteller/react-native-storyteller-sdk-gam'; // Initialize the GAM module with your configuration setupGAMModule({ customNativeTemplateId: { stories: 'YOUR_STORIES_TEMPLATE_ID', clips: 'YOUR_CLIPS_TEMPLATE_ID', }, bottomBannerAdUnit: '/YOUR_NETWORK_CODE/clips-bottom-banner-ad-unit', customKvps: { custom_key: 'custom_value', }, }); ``` Configuration parameters are explained below: - `customNativeTemplateId` - If you have worked with the Storyteller Delivery team to setup Custom Native Ads, you will need to supply their IDs here. If you are only using Stories (but not Clips) it is only necessary to supply one property of this struct - `bottomBannerAdUnit` - Optional GAM ad unit ID used for Clips bottom banner ads. Provide this when your tenant or collection is configured to show Clips bottom banner ads. - `customKvps` - Optional string key-value pairs, included when Storyteller ad tracking is enabled. The Storyteller GAM SDK passes a default set of KVPs to GAM to allow targeting based on the content of the Stories/Clips the user is viewing. If you have any additional parameters that you need to be able to target by, these should be passed here. Note that the SDK will not inherit any KVPs being set in the rest of your app. `setupGAMModule` and `setupAdMobModule` replace only the active Google ads family, so GAM and AdMob remain mutually exclusive while a supported VAST or Android IMA module stays registered. To remove GAM or AdMob without changing other ads modules, call `disableGoogleAdsModule()`: ```typescript import { disableGoogleAdsModule } from '@getstoryteller/react-native-storyteller-sdk-gam'; disableGoogleAdsModule(); ``` Disabling or replacing the Google ads module completes any pending JavaScript ad-unit requests with an empty result. It does not remove the consumer-owned `getAdUnitForRequest` subscription or clear a native ad-request enricher registered by the host app. ### Android Native Request Enrichment Android integrations that need APS, Nimbus, or another bidder SDK to mutate the prepared `AdManagerAdRequest.Builder` can register a native enricher before JavaScript calls `setupGAMModule`. Builder and bidder response objects stay in Android code; they are not serialized through React Native. ```kotlin import com.storytellersdkgam.StorytellerGamAdRequestEnricher import com.storytellersdkgam.StorytellerSdkGamModule // Replace this body with your bidder SDK's prepared-request integration. StorytellerSdkGamModule.registerAdRequestEnricher( StorytellerGamAdRequestEnricher { requestInfo, builder, completion -> bidder.load(requestInfo.itemInfo.contentId) { bidderBuilder, error -> when { bidderBuilder != null -> completion.onSuccess(bidderBuilder) error != null -> completion.onFailure() else -> completion.onNoFill() } } } ) ``` The callback may call `completion.onSuccess(builder)` immediately for synchronous enrichment or complete later after host-owned asynchronous work. Call exactly one of `onSuccess`, `onNoFill`, or `onFailure` for each active request. `onNoFill` and `onFailure` continue with a clean Storyteller request without bidder enrichment; they do not fail the ad request. The host owns bidder timeouts and must eventually complete the callback. Duplicate or post-cancellation completions are ignored. Storyteller reapplies its own targeting when a replacement builder is returned. Call `StorytellerSdkGamModule.clearAdRequestEnricher()` to remove the hook before a later `setupGAMModule` call. ### iOS Native Request Enrichment iOS integrations using Storyteller iOS 11.6.0 or newer can register the corresponding native Swift hook before JavaScript calls `setupGAMModule`. The callback runs asynchronously on the main actor and receives the prepared Google `Request` by reference, so Google and bidder objects remain entirely in native code. ```swift import GoogleMobileAds import StorytellerSDK import react_native_storyteller_sdk_gam StorytellerGamSdk.registerAdRequestEnricher { requestInfo, request in let bidderTargeting = await bidder.targeting(for: requestInfo) var customTargeting = request.customTargeting ?? [:] customTargeting.merge(bidderTargeting) { _, bidderValue in bidderValue } request.customTargeting = customTargeting } ``` Register and clear the hook from the main actor. The native callback may complete immediately or await host-owned bidder work. It mutates the prepared request in place and returns only when enrichment is complete; do not retain the request after the callback returns. Call `StorytellerGamSdk.clearAdRequestEnricher()` to remove the hook before a later `setupGAMModule` call. This is a native iOS API and is intentionally absent from the React Native JavaScript spec. ### Storyteller AdMob Ads The GAM package also exposes AdMob setup for apps that use AdMob instead of Google Ad Manager. Only one Google ads module should be active at a time; call either `setupGAMModule` or `setupAdMobModule`. ```typescript import { setupAdMobModule } from '@getstoryteller/react-native-storyteller-sdk-gam'; setupAdMobModule({ nativeAdUnit: 'ca-app-pub-xxx/native', bannerAdUnit: 'ca-app-pub-xxx/banner-fallback', bottomBannerAdUnit: 'ca-app-pub-xxx/clips-bottom-banner', enableBannerAdPriority: false, customKvps: { custom_key: 'custom_value', }, }); ``` Configuration parameters are explained below: - `nativeAdUnit` - Required AdMob ad unit ID used for native ads. - `bannerAdUnit` - Optional AdMob banner ad unit ID used as a fallback for full-screen placements. - `bottomBannerAdUnit` - Optional AdMob banner ad unit ID used for Clips bottom banner ads. - `enableBannerAdPriority` - Optional boolean, default `false`. When `true`, banner ads are attempted before native ads when `bannerAdUnit` is configured. - `customKvps` - Optional key-value pairs passed to AdMob as network extras when ad tracking is enabled. Types exported by the GAM package (import for type safety): ```ts import type { AdRequestPayload, StoriesAdRequest, ClipsAdRequest, ItemInfo, Category, SetupGAMConfiguration, SetupAdMobConfiguration, } from '@getstoryteller/react-native-storyteller-sdk-gam'; ``` GAM full-screen ads require a `getAdUnitForRequest` listener that selects the Google Ad Manager ad unit for each request. Each event includes an opaque `requestId`; return it unchanged with `setAdUnitResult({ requestId, adUnitId })`. Return an empty `adUnitId` when no GAM ad unit should be used. Respond promptly: the bridge waits up to one second on Android and five seconds on iOS, then returns an empty ad unit. Results for requests already removed from the pending map have no effect. AdMob uses the static ad units supplied to `setupAdMobModule` and does not use this listener. GAM bottom banners likewise use the static `bottomBannerAdUnit` rather than the full-screen ad-unit listener. ### Basic Example === "v11.0.0+" ```typescript import { useEffect } from 'react'; import type { EventSubscription } from 'react-native'; import { getAdUnitForRequest, setAdUnitResult, type AdRequestPayload, } from '@getstoryteller/react-native-storyteller-sdk-gam'; const STORIES_AD_UNIT = '/YOUR-CONFIGURATION'; const CLIPS_AD_UNIT = '/YOUR-CONFIGURATION'; function MyComponent() { useEffect(() => { const subscription: EventSubscription = getAdUnitForRequest( ({ requestId, adRequest }: { requestId: string; adRequest: AdRequestPayload }) => { // Determine which ad unit to use based on the request type let adUnitId = ''; if (adRequest.stories) { adUnitId = STORIES_AD_UNIT; } else if (adRequest.clips) { adUnitId = CLIPS_AD_UNIT; } // Return the ad unit ID with the matching requestId setAdUnitResult({ requestId, adUnitId }); } ); return () => subscription.remove(); }, []); // ... rest of your component } ``` === "v10.x" ```javascript const { GET_AD_UNIT_FOR_REQUEST } = StorytellerGAMAds.getConstants(); constructor(props: DemoAppProps) { const storytellerEvent = new NativeEventEmitter(StorytellerGAMAds); storytellerEvent.addListener(GET_AD_UNIT_FOR_REQUEST, this._onAdUnitRequested); } _onAdUnitRequested = (event: AdUnitRequest) => { if(event.adRequest.stories) { StorytellerGAMAds.setAdUnitResult({ adUnitId: '[storiesAdUnitId]' }); } else if (event.adRequest.clips) { StorytellerGAMAds.setAdUnitResult({ adUnitId: '[clipsAdUnitId]' }); } }; ``` ### Advanced Example with Request Details You can access detailed information about the ad request to make more sophisticated decisions: ```typescript import { useEffect } from 'react'; import type { EventSubscription } from 'react-native'; import { getAdUnitForRequest, setAdUnitResult, type AdRequestPayload, } from '@getstoryteller/react-native-storyteller-sdk-gam'; function MyComponent() { useEffect(() => { const subscription: EventSubscription = getAdUnitForRequest( ({ requestId, adRequest }: { requestId: string; adRequest: AdRequestPayload }) => { let adUnitId = ''; if (adRequest.stories) { const { placement, categories, story } = adRequest.stories; console.log('Stories ad request:', { placement, categories, storyCategories: story.categories, adIndex: adRequest.stories.adIndex, }); // Use different ad units based on placement or categories adUnitId = '/YOUR_NETWORK_CODE/stories-native-ad-unit'; } else if (adRequest.clips) { const { collection, clip } = adRequest.clips; console.log('Clips ad request:', { collection, clipCategories: clip.categories, nextClipCategories: adRequest.clips.nextClip?.categories, adIndex: adRequest.clips.adIndex, }); // Use different ad units based on collection or categories adUnitId = '/YOUR_NETWORK_CODE/clips-native-ad-unit'; } setAdUnitResult({ requestId, adUnitId }); }, ); return () => subscription.remove(); }, []); // ... rest of your component } ``` ## AdRequestPayload The GAM and VAST packages export an `AdRequestPayload` containing either Stories or Clips metadata. IMA callbacks receive the Clips object directly. The core `getAdsForList` event has a smaller contract, described under [Custom Ads](#custom-ads). ```ts // See package exports for exact type definitions // Shown here for clarity only type Category = { name: string; externalId: string; }; type ItemInfo = { contentId?: string; categories: ReadonlyArray; }; type AdRequestPayload = { stories?: { placement: string; categories: ReadonlyArray; story: ItemInfo; adIndex: number; }; clips?: { collection: string; clip: ItemInfo; nextClip?: ItemInfo; adIndex: number; }; }; ``` Stories requests include: - placement: string — uniquely identifies where Stories are shown - categories: string[] — categories assigned to the stories list - story: ItemInfo — metadata about the story after which the ad will be placed - adIndex: number — ad position supplied by the native integration Clips requests include: - collection: string — collection identifier - clip: ItemInfo — metadata about the clip for which the ad is requested - nextClip: ItemInfo | undefined — optional next-Clip metadata. Android supplies category metadata only, and omits `nextClip` when those categories are empty. iOS can also include the next Clip content ID. - adIndex: number — ad position supplied by the native integration `ItemInfo.contentId` is available on Android 11.6.3+ and iOS 11.7.0+ in GAM and VAST dynamic request callbacks for the current Story or Clip. The iOS callbacks can also include it in optional next-Clip metadata; the Android React Native bridge does not supply `nextClip.contentId`. Core custom-ad requests include the current Story or Clip content ID but do not expose next-Clip metadata in their public contract. The TypeScript field remains optional for compatibility. ## Storyteller IMA Pre-roll Ads (Android Only) The optional IMA package adds one Google IMA pre-roll before the initially opened Clip on supported Android phone Clips routes. It is independent from standard between-Clips GAM, AdMob, and VAST ads: it is not part of `betweenClipsAdProviderOrder` and does not provide Stories, banners, mid-rolls, repeated cadence, or Android TV ads. Install the package at the same version as the core SDK: ```bash yarn add @getstoryteller/react-native-storyteller-sdk-ima ``` The package is excluded from iOS autolinking. Calling a setup function or `disableImaModule()` on iOS throws an Android-only error. Guard these calls with `Platform.OS === 'android'` in shared application code. Choose one setup mode during app startup. Storyteller can build the IMA tag from GAM fields: ```typescript import { setupImaGamModule } from '@getstoryteller/react-native-storyteller-sdk-ima'; setupImaGamModule({ adUnit: '/YOUR_NETWORK_CODE/clips-preroll', descriptionUrl: 'https://example.com/clips', contentUrl: 'https://example.com/clips/content', customParams: { section: 'sports', }, tagParameters: { npa: '1', }, }); ``` Alternatively, provide one complete HTTPS IMA/VAST tag: ```typescript import { setupImaAdTagModule } from '@getstoryteller/react-native-storyteller-sdk-ima'; setupImaAdTagModule({ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?...', }); ``` To remove the active IMA integration at runtime, for example when applying an ads configuration where IMA is disabled, call `disableImaModule()`. This unregisters only the IMA module and leaves the configured GAM, AdMob, or VAST module unchanged. ```ts import { disableImaModule } from '@getstoryteller/react-native-storyteller-sdk-ima'; disableImaModule(); ``` Both modes accept an optional synchronous or asynchronous `requestConfiguration(adRequest)` callback. It receives the collection ID, current Clip metadata, optional next-Clip category metadata, and ad index. On this Android-only bridge, `nextClip.contentId` is not supplied. The callback is resolved once per native request. Supplied fields override their static fallback values; omitted fields retain those fallback values. Supplied `customParams` and `tagParameters` maps replace the corresponding static maps rather than merging individual keys. In GAM mode return `adUnit`, `descriptionUrl`, and any optional GAM fields; in complete-tag mode return `adTagUrl`. Fields belonging to the other mode are ignored. Resolve promptly: the Android bridge has a one-second response window before it uses the static fallback. ```typescript import { setupImaGamModule } from '@getstoryteller/react-native-storyteller-sdk-ima'; // Implement selectAdUnit in your app. setupImaGamModule({ adUnit: '/YOUR_NETWORK_CODE/default-preroll', descriptionUrl: 'https://example.com/clips', requestConfiguration: async ({ collection, clip, adIndex }) => ({ adUnit: selectAdUnit(collection, clip.contentId), descriptionUrl: 'https://example.com/clips', contentUrl: clip.contentId ? `https://example.com/clips/${clip.contentId}` : undefined, customParams: { collection, adIndex: String(adIndex), }, }), }); ``` `vastLoadTimeoutMs` and `mediaLoadTimeoutMs` are optional positive-integer IMA timeouts in milliseconds; omitting them keeps the Google IMA defaults. Without a callback, GAM mode requires `adUnit` and `descriptionUrl`, and complete-tag mode requires `adTagUrl`. Callback-only setup is supported; return the required fields for the selected mode and provide static fallbacks when possible. In complete-tag mode, an unresolved or invalid tag skips pre-roll. GAM mode still builds a tag from the resolved fields, so missing ad-unit or description values can produce an unusable ad request. Static GAM description/content URLs must be valid HTTPS URLs no longer than 8,192 characters. Invalid callback URL values are discarded so the static value can be used. A complete `adTagUrl` must use HTTPS and be no longer than 8,192 characters. If the JavaScript callback throws, rejects, or misses the native response window, the module falls back to the static setup values; late callback responses are ignored. Avoid logging complete tags or targeting values because they may contain sensitive information. In GAM mode, pass unencoded `customParams` values. The module encodes custom targeting into `cust_params`, generates size, correlator, playback and bid parameters, then applies `tagParameters` last. A top-level override can therefore replace a generated field. The final generated tag also has the 8,192-character HTTPS limit. Enable the opening placement explicitly in the Clips presentation configuration: ```typescript import { Platform } from 'react-native'; import StorytellerSdk from '@getstoryteller/react-native-storyteller-sdk'; if (Platform.OS === 'android') { StorytellerSdk.openCollection('YOUR_COLLECTION_ID', null, null, { preRollEnabled: true, bottomBannerEnabled: false, }); } ``` The placement still requires remote eligibility and the supported Android Clips route. Setting `preRollEnabled` does not make IMA a between-Clips provider. ## Storyteller VAST Ads The VAST package exposes the native Storyteller VAST integration for apps that need VAST-backed full-screen Story or Clip ads. Install it alongside the core SDK: ```bash npm install @getstoryteller/react-native-storyteller-sdk npm install @getstoryteller/react-native-storyteller-sdk-vast ``` The VAST package exports its configuration, callback, diagnostics, URL-format, and request payload types: ```typescript import type { SetupVASTConfiguration, SetupGAMVASTConfiguration, SetVASTRequestParametersResult, VASTDiagnosticsEvent, VASTURLFormat, AdRequestPayload, StoriesAdRequest, ClipsAdRequest, ItemInfo, Category, } from '@getstoryteller/react-native-storyteller-sdk-vast'; ``` `VASTURLFormat` is `'pathSegment' | 'queryString'`. `SetupVASTConfiguration` contains `baseUrl`, optional `requestParameters`, `urlFormat`, `enableDebugLogging`, and `usesRequestParametersCallback`. `SetupGAMVASTConfiguration` contains `adUnit`, `descriptionUrl`, and optional `contentUrl`, `customParams`, `tagParameters`, and `enableDebugLogging`. ### Generic VAST Use `setupVASTModule` when your app already has a VAST tag endpoint and can provide request parameters directly. ```typescript import StorytellerVastSdk from '@getstoryteller/react-native-storyteller-sdk-vast'; StorytellerVastSdk.setupVASTModule({ baseUrl: 'https://pubads.g.doubleclick.net/gampad/ads', urlFormat: 'queryString', requestParameters: { iu: '/21775744923/external/single_preroll_skippable', sz: '640x480', gdfp_req: '1', output: 'vast', unviewed_position_start: '1', env: 'vp', }, }); ``` Configuration parameters: - `baseUrl` - Required HTTPS VAST tag endpoint. - `requestParameters` - Optional static string key-value parameters appended to each VAST request. Dynamic callback values are merged over static values, with dynamic values winning for matching keys. - `urlFormat` - Optional serialization mode. Use `queryString` for normal `?key=value` query parameters or `pathSegment` for path-segment serialization. Defaults to `pathSegment`. - `enableDebugLogging` - Optional Android-only verbose VAST logging flag, default `false`; ignored on iOS. - `usesRequestParametersCallback` - Optional, default `false`. When `true`, native code emits `getVASTRequestParametersForRequest` and waits up to one second for `setVASTRequestParametersResult`; if the response is late, the request continues without dynamic parameters. Generic VAST adds playback, size and bid parameters automatically; your static or dynamic parameters override matching generated keys. Pass raw, unencoded parameter keys and values; the SDK encodes them. `pathSegment` appends `/key=value` segments and `queryString` appends query pairs. Use a base endpoint without an existing query or fragment for path-segment mode. The final VAST tag URL must be HTTPS and within 2,048 characters on Android or 2,048 UTF-8 bytes on iOS after serialization. When `usesRequestParametersCallback` is enabled, subscribe before presenting Storyteller content and return a result with the same `requestId`: ```typescript import StorytellerVastSdk from '@getstoryteller/react-native-storyteller-sdk-vast'; const subscription = StorytellerVastSdk.getVASTRequestParametersForRequest(({ requestId, adRequest }) => { StorytellerVastSdk.setVASTRequestParametersResult({ requestId, requestParameters: { content_type: adRequest.stories ? 'story' : 'clip', }, }); }); // Later: subscription.remove(); ``` `SetVASTRequestParametersResult` contains `requestId` and optional `requestParameters`. Omit `requestParameters` or return `{}` to use only the static parameters. Responses received after the native timeout do not change that ad request. GAM VAST setup uses its static configuration and does not use this dynamic callback. ### GAM VAST Use `setupGAMVASTModule` when you want the SDK to build Google Ad Manager VAST tag URLs without integrating Google Mobile Ads or IMA. ```typescript import StorytellerVastSdk from '@getstoryteller/react-native-storyteller-sdk-vast'; StorytellerVastSdk.setupGAMVASTModule({ adUnit: '/21775744923/external/single_preroll_skippable', descriptionUrl: 'https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags', tagParameters: { sz: '640x480', unviewed_position_start: '1', }, }); ``` Configuration parameters: - `adUnit` - Required GAM ad unit path serialized as `iu`. - `descriptionUrl` - Required HTTPS page or content URL serialized as `description_url`. - `contentUrl` - Optional HTTPS content URL serialized as `url`. - `customParams` - Optional string custom targeting encoded into `cust_params`. Supply raw values; the SDK encodes the inner targeting pairs and the enclosing URL parameter. - `tagParameters` - Optional top-level GAM VAST tag parameters applied after SDK defaults. They can override generated fields, including `iu`, size, correlator, playback/bid parameters, or `cust_params`. - `enableDebugLogging` - Optional Android-only verbose VAST logging flag, default `false`; ignored on iOS. GAM VAST generates a fresh correlator and request size, playback/mute and bid parameters for each request. Both setup modes return creatives for full-screen Story or Clip ad slots; the VAST package does not add a separate IMA-style pre-roll player. `setupVASTModule` and `setupGAMVASTModule` replace only the active VAST family, so VAST and GAM VAST remain mutually exclusive while supported GAM, AdMob, and Android IMA modules stay registered. This changes the previous iOS behavior where VAST setup replaced the complete Storyteller module list. Apps that require a VAST-only module set should explicitly disable the Google family first: ```typescript import { disableGoogleAdsModule } from '@getstoryteller/react-native-storyteller-sdk-gam'; import StorytellerVastSdk from '@getstoryteller/react-native-storyteller-sdk-vast'; disableGoogleAdsModule(); StorytellerVastSdk.setupVASTModule({ baseUrl: 'https://publisher.example/vast', }); ``` To remove VAST or GAM VAST without changing other ads modules, call `disableVASTModule()`: ```typescript import { disableVASTModule } from '@getstoryteller/react-native-storyteller-sdk-vast'; disableVASTModule(); ``` Disabling or replacing the VAST family completes pending request-parameter callbacks with empty parameters. Consumer-owned request and diagnostics subscriptions remain installed until the consumer removes them. ### VAST Diagnostics The VAST package emits diagnostics separately from user activity analytics. Subscribe while testing or when collecting support data: ```typescript import StorytellerVastSdk from '@getstoryteller/react-native-storyteller-sdk-vast'; const subscription = StorytellerVastSdk.onVASTDiagnostics(event => { console.log('VAST diagnostic', event.type, event.errorType); }); // Later: subscription.remove(); ``` Event `type` values include `requestStarted`, `requestCompleted`, `requestFailed`, `parseFailed`, `wrapperResolved`, `mappingFailed`, and `mediaSelectionFailed`. Some fields are platform-specific; handle optional fields defensively. Android commonly includes slot IDs, timing, HTTP/error codes and available media MIME types; iOS uses error names and messages. Diagnostics are emitted independently of `enableDebugLogging`. Avoid logging full payloads or URLs when they may contain targeting data. `VASTDiagnosticsEvent` exports the complete optional diagnostics payload: `slotId`, `baseUrl`, `urlFormat`, `parameterCount`, `totalMs`, `vastVersion`, `adCount`, `wrapperDepth`, `errorType`, `errorCode`, `httpStatus`, `cause`, `availableMimes`, `candidateCount`, and `message`. VAST modules support full-screen Story and Clip ads. Clips bottom banner ads are not part of the VAST protocol surface and are not supported by the VAST package. ## Custom Ads If your tenant is configured for client-supplied ads, subscribe to `StorytellerSdk.getAdsForList` and complete each native request with either `completeAdRequest(ad)` or `failAdRequest(error)`. The event receives `{ stories?: { placement, categories, story }, clips?: { collection, clip } }` directly, without a `requestId` or nested `adRequest`. Current item metadata includes optional `contentId` and category objects with `name` and `externalId`. Unlike the GAM/VAST payload, its public type does not include `adIndex` or `nextClip`; do not rely on extra iOS-native fields. Each reply returns `void` and completes the current pending custom-ad callback. The bridge does not provide per-request correlation; use one owner for the listener and avoid overlapping custom-ad work. Remove the subscription only after pending work is completed or failed. The following is a listener excerpt; implement `loadAdForRequest` in your app: ```typescript import { useEffect } from 'react'; import StorytellerSdk, { type StorytellerAd } from '@getstoryteller/react-native-storyteller-sdk'; function MyComponent() { useEffect(() => { const subscription = StorytellerSdk.getAdsForList(async adRequest => { try { const ad: StorytellerAd = await loadAdForRequest(adRequest); StorytellerSdk.completeAdRequest(ad); } catch (error) { StorytellerSdk.failAdRequest(error instanceof Error ? error.message : 'No ad available'); } }); return () => subscription.remove(); }, []); } ``` ## StorytellerAd For type-safe action destinations, import the exported `StorytellerAdActionType` union or the runtime `StorytellerAdActionKind` enum: ```typescript import { StorytellerAdActionKind, type StorytellerAd, type StorytellerAdActionType, } from '@getstoryteller/react-native-storyteller-sdk'; const actionType: StorytellerAdActionType = 'web'; const runtimeActionType = StorytellerAdActionKind.web; ``` The `StorytellerAd` object which the SDK expects to be returned contains the following properties: - `id: string` - a unique ID for the Ad in question. - `advertiserName: string` - the name of the advertiser - used in place of the Story title on the Ad Page. - `advertiserDescription?: string` - optional advertiser description. This is consumed by iOS; the linked Android SDK image/video ad factories do not expose this field. - `advertiserLogoURL?: string` - optional advertiser logo URL. This is consumed by iOS; the linked Android SDK image/video ad factories do not expose this field. - `image?: string` - the image to display for the ad. - `video?: string` - the video to display for the ad. Note that if both an image and a video are supplied, then the `video` is preferred. - `playcardUrl?: string` - the image to display as a placeholder if a video asset is still loading. It could be set, for example, as the first frame of the video. This property is unused for image ads. - `duration?: number` - duration in seconds. Use whole seconds for cross-platform consistency: Android converts this value to an integer. Omit it for videos to use the media duration; a positive supplied duration can control Story playback duration on iOS. Image ads default to 5 seconds when omitted. `null` is not part of the public TypeScript type. - `trackingPixels: StorytellerAdTrackingPixel[]` - an array of 1x1 ad tracking pixels which will be triggered when an ad is loaded and throughout its playback - properties detailed below. - `action?: StorytellerAd['action']` - optional property which describes what should happen when a user presses an action button on the ad. Omit this property when no action is required. - `adFormat: string` - required format identifier on both bridges; the linked Android SDK image/video ad factories always create custom native ads. - `responseIdentifier?: string` - optional response identifier. This is consumed by iOS; the linked Android SDK image/video ad factories do not expose this field. The `StorytellerAdTrackingPixel` object contains the following properties: - `eventType: string` - string describing the type of the tracking pixel - possible values are "impression", "videoStart", "firstQuartile", "midpoint", "thirdQuartile", "videoComplete", "videoPause", "videoResume". - `url: string` - the tracking pixel URL. The `action` object (`NonNullable`) contains the following properties: - `type: StorytellerAdActionType` - action destination. Possible values are: `web` (will direct the user to an in-app browser); `inApp` (will direct the user to a location within the integrating app); `store` (will direct the user to the App Store); `externalApp` (will direct the user to another App if the user has it installed) - `urlOrStoreId: string` - string describing the destination. For `web` types, this should be a valid HTTPS URL. For `inApp` types, this should be a valid deeplink into the integrating app - when a user presses an action button on an ad the SDK emits `StorytellerSdk.userNavigatedToApp` with this URL for your app to direct the user to the correct destination. For `store` types, this should be the URL of the App you wish to link to on the App Store (e.g. [https://apps.apple.com/us/app/testflight/id899247664](https://apps.apple.com/us/app/testflight/id899247664)) - `text?: string` - label for the action button. Supply a non-empty label such as `Learn More` for consistent behavior. On the current iOS SDK, omitting or blanking this label suppresses the action. There is no cross-platform `Learn More` default. ## Ad Encoding For client-supplied portrait video creatives, use the following recommended encoding profile. The React Native bridge does not transcode or validate these encoding values: - Width: 540 - Height: 960 - Bitrate: 1500 kbps - MaxBitrate - 1500 kbps - FrameRate - 30 fps - Audio: AAC 128 kbps bitrate, 48k sampling ## Clips placements and provider selection Set `preRollEnabled` and `bottomBannerEnabled` explicitly whenever you supply an `adConfiguration` object: omitted boolean fields in that object become `false` in the React Native bridges. The configuration permits a placement; it does not bypass tenant settings, consent or ad-provider eligibility. On Android, `preRollEnabled: false` suppresses both standard opening ads and IMA opening pre-roll while later between-Clips slots remain eligible. On Android, use `adConfiguration` when opening a Clip collection/item or configuring a Clips row, grid or Embedded Clips view. On iOS, the current bridge applies these placement flags to `openCollection`, `openCollectionWithCategory`, and Embedded Clips; row/grid configurations do not apply them. `betweenClipsAdProviderOrder` is Android-only and controls standard full-screen Clips provider attempts: - Omitted or `null`: preserve registered module order and custom-module behavior. Setup appends the chosen module after retained modules, so setup order can affect priority. - `[]`: disable standard between-Clips provider attempts for the presentation. - A populated array: try only the listed recognized providers, in order. Duplicate providers use their first occurrence; unavailable providers are skipped. Custom modules without a recognized provider are excluded. ```typescript import { StorytellerAdProvider, type StorytellerClipsAdConfiguration, } from '@getstoryteller/react-native-storyteller-sdk'; const adConfiguration: StorytellerClipsAdConfiguration = { preRollEnabled: true, bottomBannerEnabled: true, betweenClipsAdProviderOrder: [StorytellerAdProvider.vast, StorytellerAdProvider.gam], }; ``` `StorytellerAdProvider.vast` selects generic or GAM VAST. GAM and AdMob share one Google family, so only the currently registered one is available. IMA and bottom banners use independent paths; this array does not control them. iOS ignores the provider-order field. Bottom banners require a Google-family module with `bottomBannerAdUnit`, `bottomBannerEnabled: true`, and remote eligibility. ## Android 12 Clips cadence overrides `StorytellerClipsAdConfiguration` accepts optional Android-only `frequency` and `initialIndex` values on native 12.0.0. They apply to the selected presentation's eligible standard between-Clips ad strategy; they cannot enable ads or bypass consent, provider or remote eligibility gates. They do not change IMA pre-roll or bottom banners. - `frequency`: positive integer interval. Omitted, null or non-positive values inherit remote frequency. - `initialIndex`: non-negative integer. `0` is the special opening-ad value; positive values are one-based content positions (`2` means after the second Clip). Omitted, null or negative values inherit the remote initial index. Pass these fields alongside the existing configuration flags when opening a collection or configuring a Clips view: ```typescript import type { StorytellerClipsAdConfiguration } from '@getstoryteller/react-native-storyteller-sdk'; const adConfiguration: StorytellerClipsAdConfiguration = { preRollEnabled: true, bottomBannerEnabled: false, frequency: 3, initialIndex: 2, }; ``` The current iOS bridge ignores these two cadence fields. Include the boolean flags explicitly, as described above. ## Android bottom banner fetch mode `bottomBannerFetchMode` and `StorytellerBottomBannerFetchMode` require React Native **12.1.1** or later, which uses Android native 12.1.1. `StorytellerClipsAdConfiguration` accepts an optional Android-only `bottomBannerFetchMode` that controls when Clips bottom banners are requested for the selected presentation: - `StorytellerBottomBannerFetchMode.justInTime` (default) requests a bottom banner only for the current Clip. The next Clip's banner is requested once that Clip becomes current. Omitted, null or unrecognized values use this mode, so upgrading alone does not change behavior. - `StorytellerBottomBannerFetchMode.prefetch` also prepares the bottom banner for the next content Clip while the current Clip plays, so it is ready when the viewer swipes. It prepares only one Clip ahead and reuses that banner or its in-flight request instead of loading it again. It can request banners for Clips the viewer never reaches. The fetch mode only changes request timing. Bottom banners still need `bottomBannerEnabled`, a configured bottom-banner ad unit and remote eligibility. ```typescript import { StorytellerBottomBannerFetchMode, type StorytellerClipsAdConfiguration, } from '@getstoryteller/react-native-storyteller-sdk'; const adConfiguration: StorytellerClipsAdConfiguration = { preRollEnabled: true, bottomBannerEnabled: true, bottomBannerFetchMode: StorytellerBottomBannerFetchMode.prefetch, }; ``` Pass it when opening a collection or configuring a Clips row, grid or Embedded Clips view. The current iOS bridge ignores this field.