Skip to main content

Companion ads

A companion ad is a banner that appears next to the video player while an ad plays, for example, a brand logo with a "Watch now" button, or a 320x50 image banner. The @quickplay/player library provides createCompanionPresentation to show these companions. Your app supplies the container element, decides what happens when a viewer taps a companion, and receives events for analytics.

Supported companion types​

Companion typeSupportedAd deliveryRendered byNotes
Custom CTAYesAny provider, through trafficking parametersSDKIcon, title, optional description, and one button. Never retained after the ad.
Standard (GPT)YesGoogle IMA with VAST companionsIMA, in SDK-owned GPT slotsRequires the GPT script and an ad unit path. Default sizes are 320x50 and 300x250.
Standard (static image)YesDatazoom MediaTailor SSAI, or any provider that fills AdInfo.companionAdsSDKImage resources only. Your app dispatches the tracking URLs.
CarouselNoNot applicableNot applicableThe SDK doesn't render carousels. Your app can render its own, and a standard banner can act as a fallback.
HTML, iframe, or takeoverNoNot applicableYour appIf ad_type=webview, the SDK shows nothing so your app can use the area.

For the Android equivalent, see Android companion ads.

note

Companion ads are available for web browsers only. They are not supported on Smart TV platforms, including WebMAF and PS5.

Choose a companion format​

The presentation shows one companion at a time. Each ad can use one of three flavours, depending on how the ad is delivered.

FlavourWhen to use itWhere the creative comes fromWho renders itWho tracks it
Custom CTAYou want a branded banner with an icon, copy, and a button.Ad trafficking parametersThe SDKYour app, through onEvent
Standard (GPT)You serve ads with Google IMA and have VAST companions.IMA VAST companionsIMA, into SDK-owned Google Publisher Tag (GPT) slotsIMA
Standard (static)You use Datazoom MediaTailor SSAI and its ads include static image companions.AdInfo.companionAdsThe SDK, as a static imageYour app, through TrackingRequested

When more than one flavour is available for an ad, the player picks one using these rules:

  • A custom CTA takes precedence while its icon loads and while it is shown.
  • If the CTA can't render, a standard banner can take its place.
  • A ready static banner takes precedence over a GPT banner.
  • Static banners support image resources only. They don't load HTML or iframes.

Your app, not the SDK, is responsible for:

  • Page layout, orientation, and picture-in-picture (PiP) behavior.
  • Deep links and operations such as watchlist or reminders.
  • Takeover rendering. If a trafficking parameter sets ad_type=webview, the SDK shows no companion so that your app can use the area.
  • Carousel, contextual ads, and advertiser tabs. The SDK doesn't render them. A carousel ad still permits a standard banner as a fallback.

Before you begin​

  • Install @quickplay/player.

  • For GPT companions, load the GPT script on your page. The SDK registers its slots through googletag.cmd, so GPT can still be loading when you create the presentation.

    <script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
  • Static companions don't need GPT or an ad unit path.

tip

The SDK registers its slots with the companion service only, never with PubAdsService. They can't request regular display ads, and your other GPT slots aren't affected.

Create the companion presentation​

Create the presentation before the first adstart event. Keep one presentation for the lifetime of the IMA display container, and release it when that container is destroyed.

import { createCompanionPresentation } from '@quickplay/player';

const companion = createCompanionPresentation({
container: document.getElementById('companion')!,
adPlayer,
configuration: {
native: { adUnitPath: '/1234/companion' }, // GPT companions only
nativeSizes: [{ width: 320, height: 50 }],
persistCompanionAfterAd: true,
persistStaticCompanionAfterAd: true,
},
actions: {
onAction: async ({ url }, updateState) => {
updateState({ label: 'Opening…', disabled: true });
await router.open(url);
updateState({ label: 'Opened', disabled: false });
return true;
},
},
onEvent: event => {
if (event.type === 'TrackingRequested') {
// Dispatch these URLs through your app's approved tracking client.
event.urls.forEach(url => companionTracking.send(url));
}
analytics.track(event);
},
});
PropertyRequiredDescription
containerYesThe element the SDK draws the companion into.
adPlayerYesThe ad player to follow.
configurationNoFormats, sizes, retention, and theme. See Configuration reference.
actions.onActionNoHandles taps on a companion. See Handle taps.
onEventNoReceives presentation events. See Listen to events.

Manage the presentation​

Use these methods across content changes and for teardown:

companion.setVisible(false); // Hide while PiP or another ad UI is showing
companion.setVisible(true); // Restore the same creative and slots
companion.setAdPlayer(nextAdPlayer); // Clear, then follow the new player
companion.setAdPlayer(undefined); // Clear and detach when playback stops
companion.release(); // Final teardown
important

When the content changes, call setAdPlayer(nextPlayer) on the existing presentation instead of creating a new one. The IMA loader keeps its GPT slot references across plays. Destroying and recreating the slots can stop later companions from rendering.

Set up a custom CTA​

A custom CTA is a banner with an icon, a title, optional description, and one button. The SDK builds it from the ad's trafficking parameters, which are a comma-separated list of key=value pairs.

ad_type=custom_cta,icon=https://cdn.example.com/brand.png,bannerTitle=Watch now,bannerDescription=Start the next episode,ctaText=Watch,ctaUrl=https://www.example.com/watch,campaign=spring,bgColour=#1A1A1A,bannerTextColor=#FFFFFF,ctaBgColor=#E53935,ctaTextColor=#FFFFFF
ParameterRequiredDescription
ad_typeNocustom_cta or custom. Omit it to let the SDK detect a CTA from its fields.
iconYesHTTP(S) URL of the icon image.
bannerTitleYesTitle text. It can't be blank.
ctaUrlYesDestination URL or an app-routed deep link.
bannerDescriptionNoAdditional copy.
ctaTextNoButton label. Defaults to Learn more.
bgColour, bannerTextColorNoBanner background and text colors.
ctaBgColor, cta_bg_color, ctaColourNoButton background color.
ctaTextColor, cta_text_colorNoButton text color.
campaign, campaign nameNoCampaign name included in presentation events.
companion_schema_versionNoOnly 1 is accepted. If omitted, 1 is used.

Keep these rules in mind when you write the parameters:

  • Parameter names are case-insensitive.
  • icon, bannerTitle, and ctaUrl must all be present. Unrelated parameters alone don't create a CTA.
  • If you supply more than one button background alias, the values must match.
  • Split on the first = only, so URL query strings are preserved. There is no way to escape a literal comma.
  • The SDK rejects the creative if it finds duplicate keys, conflicting aliases, malformed segments, or an unsupported schema version.
  • An explicit ad_type other than custom_cta or custom is never treated as a CTA.

Colors​

Colors resolve in this order: the creative, then configuration.theme, then the built-in defaults. Use #RRGGBB or opaque #FFRRGGBB values. The SDK ignores invalid or translucent colors. If text contrast is below 4.5:1, the SDK falls back to black or white. Copy and button labels are always set as text, never as HTML.

Use your own decoder​

If your ads use a different parameter format, set configuration.decode. The decoder is synchronous and must not throw on malformed ad data. Return a CompanionDecodeResult with one of the following:

  • creative to show a custom CTA.
  • reason to reject the creative.
  • suppressStandard: true when another app presentation owns the area.

Set up standard companions​

GPT companions with Google IMA​

IMA renders standard VAST companions into GPT slots that the SDK owns. To enable them:

  1. Load the GPT script on your page.
  2. Set configuration.native.adUnitPath to your companion ad unit.
  3. Set configuration.nativeSizes to the sizes you want to accept. The default is 320x50 and 300x250.

IMA owns rendering, clicks, and beacons for GPT companions, so you don't dispatch any tracking URLs for them.

If GPT was requested but googletag isn't available, the presentation emits a CapabilityLimited event.

Static companions with Datazoom MediaTailor​

For Datazoom MediaTailor SSAI, the player exposes the ad's companions as AdInfo.companionAds. Other providers can populate the same optional model. Each CompanionAd has the following fields:

FieldRequiredDescription
widthYesDeclared width of the image.
heightYesDeclared height of the image.
staticResourceYesImage URL.
idNoCompanion identifier.
clickThroughUrlNoDestination when the viewer taps the banner.
impressionTrackingUrlsNoImpression URLs your app dispatches.
clickTrackingUrlsNoClick URLs your app dispatches.

The SDK picks the largest nativeSizes entry that fits the container and has an HTTP(S) image resource. If nothing matches, no static companion shows. The image must load within imageTimeoutMs. If the container is resized during the ad, the SDK can select a different size. A retained banner that no longer fits stays hidden until the container is large enough again.

Taps on a static banner go through the same onAction callback as a custom CTA, so your app can pause playback before opening the destination.

Dispatch tracking URLs​

For static companions, the SDK doesn't send tracking pixels. Instead, it emits TrackingRequested events, and your app must send each URL exactly once.

onEvent: event => {
if (event.type === 'TrackingRequested') {
// event.tracking is 'impression' or 'click'
event.urls.forEach(url => companionTracking.send(url));
}
},
  • An impression request is emitted once per displayed static companion per ad, and only after the image has loaded and is visible.
  • A click request is emitted once per accepted tap. Repeated taps are suppressed while an action is pending and for 500 ms after it settles.
  • URLs are deduplicated and limited to HTTP(S).
  • TrackingRequested asks your app to send the URLs. It doesn't confirm that a network request succeeded.

Handle taps​

The SDK calls onAction when a viewer taps a custom CTA or a static banner.

actions: {
onAction: async ({ url, campaign, context }, updateState) => {
updateState({ label: 'Adding…', disabled: true });
await myList.add(url);
updateState({ label: 'Added', disabled: false });
return true;
},
},

onAction receives the url, the campaign, an optional ad context, and an updateState callback scoped to the current ad.

Return valueResult
trueYour app handled the action.
falseThe SDK opens the URL in a new tab with _blank and noopener,noreferrer. The default opener allows HTTP(S) URLs only.

To change the button, call updateState({ label, disabled, hidden }):

  • label changes the button text.
  • disabled disables the button.
  • hidden: true removes the CTA and lets a standard banner take its place.

Keep these behaviors in mind:

  • The button is disabled while an action runs and for a 500 ms cooldown after.
  • State resets on every new ad. To set an initial state, call setActionState during the active ad.
  • Async handlers must use the updateState callback they receive. Updates from a settled, timed-out, or earlier ad's action are ignored.
  • If the handler throws or rejects, the outcome is failed.
  • If the handler runs longer than actionTimeoutMs, the outcome is timedOut, and the SDK never falls back to the default opener later.
  • If the handler completes while the presentation is hidden, the default opener doesn't run, and the outcome is blocked.

Control visibility and lifecycle​

setVisible(false) hides the SDK-owned companion and blocks taps. It keeps the current creative, action state, and GPT slots. Asset loading and ad events keep running while hidden. When you show it again, the SDK restores a valid creative without sending a second static impression. Your app owns the PiP, fullscreen, orientation, and competing-presentation rules.

SignalBehavior
adstartClears the previous creative and action state, then decodes and loads the new ad.
adendRemoves the CTA. Keeps an already displayed banner only if retention is on for its source.
adbreakendEnds an active ad if adend was missing, then reports retention.
adbreakstartDrops a banner retained from the previous break.
adskip, IMA skip tracking, aderror, clear()Clears everything, regardless of retention.
setAdPlayerClears and rebinds to the new player without replacing GPT slots.
release()Unsubscribes and removes SDK views, listeners, and GPT slots.

Keep a banner after the ad ends​

By default, a companion disappears when its ad ends. To keep a displayed banner on screen until the next break, set the retention options. Both default to false.

OptionApplies to
persistCompanionAfterAdGPT banners
persistStaticCompanionAfterAdStatic SSAI banners

A retained banner stays until the next ad or break, an explicit clear, an error, a skip, or teardown. Keep in mind:

  • A custom CTA is never retained.
  • An image that finishes loading after the ad has ended isn't retained.
  • A Retained event fires once after content resumes while the banner is visible.
  • GPT fills that arrive between ads in an active break stay hidden until the next adstart.

Listen to events​

onEvent receives the following events:

EventWhen it fires
DisplayDecisionThe SDK chose what to show. It includes the format (or none with a reason), the size, and the campaign.
CapabilityLimitedGPT was requested but googletag is unavailable.
AssetFailedA CTA icon or static companion image failed to load or timed out.
RetainedA retained standard banner is still visible after the break.
ActionResultA tap resulted in opened, handled, blocked, failed, or timedOut.
TrackingRequestedStatic companion impression or click URLs your app must dispatch.

Events and actions include an optional context with adId, creativeId, adBreakId, adSessionId, and delivery, when the provider supplies them. An exception thrown in your listener doesn't break the presentation.

Configuration reference​

All configuration fields are optional.

OptionDefaultDescription
formats['customCta', 'standard']Formats that can be shown.
decodeBuilt-in decoderYour own trafficking parameter decoder.
native.adUnitPathNoneGPT ad unit path. Required for GPT companions.
native.googletagwindow.googletagThe GPT global.
nativeSizes320x50 and 300x250GPT slot sizes and the static sizes the SDK accepts.
persistCompanionAfterAdfalseKeep a displayed GPT banner after its ad.
persistStaticCompanionAfterAdfalseKeep a displayed static banner after its ad.
imageTimeoutMs8000Time allowed for an image to load.
actionTimeoutMs5000Time allowed for onAction to settle.
themeBuilt-in colorssurfaceColor, textColor, buttonColor, and buttonTextColor.