External VAST Ads
Render your own VAST tag through the StreamLayer Element with the externalAdData prop, in side-by-side, sidebar, or L-bar format.
Overview
By default, ad units arrive from StreamLayer Studio through the event feed. The externalAdData
prop lets your app drive the ad itself: you hand the SDK a VAST tag URL and the format you want,
and the SDK builds an ad unit from it and renders it through the same pipeline as a Studio-scheduled
ad — including VAST impression, quartile, and click-through tracking.
Use this when the creative is selected by your own ad stack rather than by StreamLayer Studio.
Requires
@streamlayer/react2.0.0 or later.
Quick Start
import { useMemo, useRef } from 'react'
import { StreamLayerProvider } from '@streamlayer/react'
import { StreamLayerSDKAdvertisement } from '@streamlayer/react/advertisement'
import '@streamlayer/react/style.css'
export const Player = () => {
const videoContainerRef = useRef<HTMLDivElement>(null)
// Memoize the object — the SDK keys the injected ad unit on its identity.
const externalAdData = useMemo(
() => ({
vastUrl: 'https://example.com/vast.xml',
uiType: 'side-by-side' as const,
}),
[]
)
return (
<div id="player-root" style={{ position: 'relative' }}>
<div ref={videoContainerRef}>
<video src="https://example.com/stream.m3u8" autoPlay controls />
</div>
<StreamLayerProvider sdkKey="your-sdk-key" event="your-event-id" containerId="player-root">
<StreamLayerSDKAdvertisement
sideBySide
videoRef={videoContainerRef}
externalAdData={externalAdData}
/>
</StreamLayerProvider>
</div>
)
}The ad unit appears as soon as the VAST tag resolves, and is removed when externalAdData changes
to a different tag or the component unmounts.
ExternalAdInput
ExternalAdInputtype ExternalAdUiType = 'side-by-side' | 'sidebar' | 'l-bar'
type ExternalAdInput = {
/** VAST tag URL — raw VAST/VMAP, or a Google Ad Manager ad-tag URL. */
vastUrl: string
/** Which format the ad unit renders in. */
uiType: ExternalAdUiType
/** Background image URL for landscape layouts. */
landscapeBackground?: string
/** Background image URL for portrait layouts. */
portraitBackground?: string
}| Field | Type | Description |
|---|---|---|
vastUrl | string | Required. VAST tag URL. Raw VAST/VMAP is fetched directly; a pubads.g.doubleclick.net tag is resolved through Google PAL. |
uiType | 'side-by-side' | 'sidebar' | 'l-bar' | Required. Where the ad unit renders. See the table below. |
landscapeBackground | string | Optional. Background image URL behind the side-by-side ad unit in landscape layouts. Defaults to the StreamLayer background. |
portraitBackground | string | Optional. Background image URL behind the side-by-side ad unit in portrait layouts. Defaults to the StreamLayer background. |
Formats
uiType | Renders as | Creative used |
|---|---|---|
'side-by-side' | Double box beside the host video | VAST linear creative, played in the SDK's own player |
'sidebar' | Ordinary sidebar slot | VAST linear creative as the sidebar media |
'l-bar' | Sidebar plus bottom banner strip | Linear creative in the sidebar; the VAST companion ad as the strip |
An 'l-bar' tag whose VAST carries no companion ad degrades to a plain sidebar — no empty strip is
reserved.
The sidebar video autoplays muted. Title and description come from the VAST <AdTitle> and
<Description> elements.
Slot Placement
Explicit host intent wins: an ad injected with uiType renders where the uiType says, regardless
of the VAST streamlayer extension. Ads that arrive from the event
feed follow the extension's root element first, then the format configured in StreamLayer Studio;
side-by-side is used only when an extension is present but neither names a format.
Because an external ad normally belongs to the side-by-side slot, useStreamLayerUI() alone can't
tell you whether to keep your sidebar and banner slots mounted for one. The
useExternalAdPlacement hook resolves this:
import { useStreamLayerUI } from '@streamlayer/react'
import {
StreamLayerSDKAdvertisement,
useExternalAdPlacement,
} from '@streamlayer/react/advertisement'
const Layout = () => {
const uiState = useStreamLayerUI()
const { externalSidebar, externalLbar } = useExternalAdPlacement(uiState.promotionExternalAd)
return (
<>
<aside style={{ width: externalSidebar || uiState.promotionSidebar ? 320 : 0 }}>
<StreamLayerSDKAdvertisement sidebar="right" externalAdData={externalAdData} />
</aside>
{externalLbar && (
<div className="banner">
<StreamLayerSDKAdvertisement banner="bottom" externalAdData={externalAdData} />
</div>
)}
</>
)
}| Return value | Meaning |
|---|---|
externalSidebar | The active external ad resolves to the sidebar or L-bar format — keep the sidebar slot sized. |
externalLbar | The active external ad resolves to L-bar and its VAST carries a companion — keep the banner slot sized. |
Both flip only once the VAST has been parsed, so the sidebar never opens on an empty creative.
VAST streamlayer Extension
streamlayer ExtensionStandard VAST carries the media, title, description, and click-through, but not the branding a
StreamLayer ad unit needs around it. Your ad server supplies that through a StreamLayer
<Extension> in the VAST response. The SDK reads it for every external ad — injected with
externalAdData or scheduled in StreamLayer Studio.
Where the SDK looks
- The SDK takes the first
<Extension>whosetypeattribute starts withstreamlayer
(for examplestreamlayerorstreamlayer/double-box/v1). Other extensions are ignored. - The first child element of that extension is its root. The fields below are read from the
root's children. - The root's element name selects the format.
DoubleBoxis the only name defined today; any other
name is still read for the fields below but does not change the format.
Fields
| Element | Attributes | Used for |
|---|---|---|
AdName | — | Internal campaign name. Parsed but not rendered. |
Backgrounds/Background/Url | orientation="landscape" | Background image of the ad unit in landscape layouts (TV and desktop web). |
Backgrounds/Background/Url | orientation="portrait" | Background image of the ad unit in portrait layouts (mobile web). |
Logos/Logo/Url | role="header" | Sponsor logo of the ad unit. |
CTA/Label | — | CTA button label for the sidebar and L-bar formats. |
CTA/Type | — | Reserved. Parsed but not used. |
width, height, and type attributes on Background and Logo describe the asset; the SDK does
not use them to pick between assets. When several Background elements share an orientation, the
first one wins.
Example
<Extensions>
<Extension type="streamlayer/double-box/v1">
<DoubleBox>
<AdName><![CDATA[Spring 2026 — Double Box]]></AdName>
<Backgrounds>
<Background orientation="landscape" width="2560" height="1440" type="image/png">
<Url><![CDATA[https://cdn.example.com/creatives/12345/bg-landscape.png]]></Url>
</Background>
<Background orientation="portrait" width="1000" height="1500" type="image/png">
<Url><![CDATA[https://cdn.example.com/creatives/12345/bg-portrait.png]]></Url>
</Background>
</Backgrounds>
<Logos>
<Logo role="header" width="300" height="75" type="image/png">
<Url><![CDATA[https://cdn.example.com/creatives/12345/logo.png]]></Url>
</Logo>
</Logos>
<CTA>
<Label><![CDATA[Shop Now]]></Label>
</CTA>
</DoubleBox>
</Extension>
</Extensions>Place <Extensions> inside the <InLine> ad, next to <Creatives>. See
Double Box Creative Specs for recommended asset sizes
and a complete sample VAST response.
Precedence and fallbacks
| Field | Extension present | Extension missing the field |
|---|---|---|
| Background | The extension image replaces the background configured in StreamLayer Studio. | Studio background, otherwise the StreamLayer default background. |
| Sponsor logo | The extension logo replaces the Studio sponsor logo. In the sidebar and L-bar formats, a 300×75 VAST companion logo is drawn in preference to it. | Studio logo, or the VAST companion logo in the sidebar and L-bar formats. |
| CTA label | The extension label replaces the Studio label (sidebar and L-bar only). | Studio label, otherwise LEARN MORE. |
An ad injected with externalAdData has no Studio configuration behind it, so the extension is
its only source of background and branding. ExternalAdInput has no field to override them from
the Host App — change the VAST response to change them.
Note: The extension is read only for external ads. Pause ads set through
pauseAdExternalUrlsignore it. See Exposed Pause Ad.
Rules and Limits
- Memoize
externalAdData. The SDK derives a stable id fromvastUrl+uiType, but a fresh
object literal on every render still churns the effect that injects and removes the ad unit. Use
useMemo, or a module-level constant when the tag is fully static. - Backgrounds are read once.
landscapeBackgroundandportraitBackgroundare applied when the
ad unit is injected and are not part of its id. Changing only the backgrounds does not update an ad
unit that is already injected; changevastUrloruiType, or remount, to apply new ones. - One injected ad at a time. The advertisement store holds a single ad unit, so only one
externalAdDatainstance per page is supported. Injecting replaces any active ad unit, and a
Studio-scheduled ad replaces an injected one. externalAdDataimplies external-ad mode. You do not also need to pass the boolean
externalAdprop for it to render.- Don't double up slots. A slot marked with the boolean
externalAdprop is the programmatic
GAM/PAL slot; it stands down for sidebar and L-bar placements so the plain slot renders the ad
once. This behavior changed in 2.0.0. - Mute the host video. Pass a
videoPlayerControllerso the host stream is muted while the ad's
audio plays. See Video Controller.
Related
- Advertising — Advertisement component integration
- Double Box Ad — Side-by-side layout setup and positioning
- Video Controller — Muting the host video during ad playback
- Double Box Creative Specs — Asset sizes and a sample VAST response
- TV Exposed Pause Ad — External VAST ads on
StreamLayerSDKTv - Integration Guide — Full Web SDK setup and configuration
Updated 8 days ago
