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. 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
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
Package Installation and Setup#
Install the GAM package alongside the core SDK:
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:
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 structbottomBannerAdUnit- 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():
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.
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.
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.
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, defaultfalse. Whentrue, banner ads are attempted before native ads whenbannerAdUnitis 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):
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#
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
}
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:
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.
// See package exports for exact type definitions
// Shown here for clarity only
type Category = {
name: string;
externalId: string;
};
type ItemInfo = {
contentId?: string;
categories: ReadonlyArray<Category>;
};
type AdRequestPayload = {
stories?: {
placement: string;
categories: ReadonlyArray<string>;
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
nextClipwhen 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:
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:
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:
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.
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.
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:
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:
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:
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.
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. UsequeryStringfor normal?key=valuequery parameters orpathSegmentfor path-segment serialization. Defaults topathSegment.enableDebugLogging- Optional Android-only verbose VAST logging flag, defaultfalse; ignored on iOS.usesRequestParametersCallback- Optional, defaultfalse. Whentrue, native code emitsgetVASTRequestParametersForRequestand waits up to one second forsetVASTRequestParametersResult; 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:
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.
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 asiu.descriptionUrl- Required HTTPS page or content URL serialized asdescription_url.contentUrl- Optional HTTPS content URL serialized asurl.customParams- Optional string custom targeting encoded intocust_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, includingiu, size, correlator, playback/bid parameters, orcust_params.enableDebugLogging- Optional Android-only verbose VAST logging flag, defaultfalse; 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:
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():
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:
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:
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:
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 thevideois 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.nullis 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<StorytellerAd['action']>) 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. Forwebtypes, this should be a valid HTTPS URL. ForinApptypes, this should be a valid deeplink into the integrating app - when a user presses an action button on an ad the SDK emitsStorytellerSdk.userNavigatedToAppwith this URL for your app to direct the user to the correct destination. Forstoretypes, 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)text?: string- label for the action button. Supply a non-empty label such asLearn Morefor consistent behavior. On the current iOS SDK, omitting or blanking this label suppresses the action. There is no cross-platformLearn Moredefault.
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.
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.0is the special opening-ad value; positive values are one-based content positions (2means 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:
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.prefetchalso 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.
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.