VMAP: Video Multiple Ad Playlist
VMAP (Video Multiple Ad Playlist) is the IAB Tech Lab XML specification that describes the ad break structure of a video: where the breaks are on the content timeline, what kind of ads each break may contain, and which ad source fills them. It answers a question VAST deliberately does not: not "which ad plays" but "when do ads play at all."
The current version is VMAP 1.1 (2018). Most documents in the wild, including everything Google Ad Manager emits, still declare version="1.0".
Why VMAP exists
VMAP solves a separation-of-powers problem. The party that owns the content (a broadcaster, a studio, a channel) often does not control the player that renders it (a distribution app, a smart TV platform, a syndication partner). The content owner still wants authority over ad policy: how many breaks, where they sit, whether a break accepts a pod, which ad server decides the ads.
VMAP is that contract in XML. The content owner publishes one VMAP document per video; any compliant player can read it and know the full commercial structure of the stream without hardcoding a single break position.
How VMAP and VAST divide the work
A VMAP document does not contain ads. Each <vmap:AdBreak> contains an <vmap:AdSource> that either references an ad tag URL (which returns VAST) or embeds a VAST document inline. The layering is strict:
| Layer | Question it answers | Spec |
|---|---|---|
| Break schedule | Where do breaks sit in the content timeline? | VMAP |
| Ad sequencing | Which ads play inside one break, in what order? | VAST 3.0+ ad pods (sequence attribute) |
| Ad description | Which media file, which trackers, which click-through? | VAST |
The ad pods guide covers the middle layer. A long-form CTV stream typically uses all three: VMAP places four mid-roll breaks, each break's ad tag returns a podded VAST response, and each ad in the pod is a normal VAST creative.
Anatomy of a VMAP document
A minimal document with a pre-roll, one mid-roll at ten minutes, and a post-roll:
<vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">
<vmap:AdBreak timeOffset="start" breakType="linear" breakId="preroll">
<vmap:AdSource id="preroll-ad" allowMultipleAds="false" followRedirects="true">
<vmap:AdTagURI templateType="vast4">
<![CDATA[https://adserver.example.com/vast?pos=preroll]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
<vmap:AdBreak timeOffset="00:10:00.000" breakType="linear" breakId="midroll-1">
<vmap:AdSource id="midroll-ad" allowMultipleAds="true" followRedirects="true">
<vmap:AdTagURI templateType="vast4">
<![CDATA[https://adserver.example.com/vast?pos=midroll1]]>
</vmap:AdTagURI>
</vmap:AdSource>
<vmap:TrackingEvents>
<vmap:Tracking event="breakStart">
<![CDATA[https://tracker.example.com/breakstart?break=midroll-1]]>
</vmap:Tracking>
</vmap:TrackingEvents>
</vmap:AdBreak>
<vmap:AdBreak timeOffset="end" breakType="linear" breakId="postroll">
<vmap:AdSource id="postroll-ad" allowMultipleAds="false" followRedirects="true">
<vmap:AdTagURI templateType="vast4">
<![CDATA[https://adserver.example.com/vast?pos=postroll]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
</vmap:VMAP>The <vmap:AdBreak> element
timeOffset(required): where the break sits. Acceptsstart,end, a timestamp (HH:MM:SS.mmm), a percentage of content duration (25%), or an ordinal position (#2for "the second opportunity").breakType(required):linear(content stops),nonlinear(overlay while content plays), ordisplay(companion slots). Multiple values can be comma-separated.breakId(optional): an identifier that shows up in tracking and debugging.repeatAfter(optional): a duration for recurring breaks, e.g. an ad break every 30 minutes of a live stream.
The <vmap:AdSource> element
<vmap:AdTagURI>: the ad server URL to call when the break is reached.templateTypedeclares what the URL returns (vast4,vast3,vast2,vast1, orproprietary).<vmap:VASTAdData>: alternatively, a complete VAST document embedded inline, saving the extra request.allowMultipleAds: whether the VAST response may play a pod of ads in this break or exactly one.followRedirects: whether the player may resolve VAST wrapper chains from this source.
Break-level tracking
<vmap:TrackingEvents> supports three events, all at the break level rather than the ad level: breakStart, breakEnd, and error. Ad-level tracking (impressions, quartiles, clicks) stays in the VAST documents that fill the break.
Where you meet VMAP in practice
- Google Ad Manager ad rules: when ad rules are enabled, GAM returns a VMAP playlist instead of a single VAST document, and the IMA SDK consumes it natively.
- SSAI stitchers: server-side insertion platforms accept a VMAP schedule as the input that tells them where to cut the content and how to fill each gap. See what is SSAI for how that pipeline works.
- Syndication: content owners ship VMAP alongside video so downstream platforms enforce the same break policy without custom integration.
Common VMAP mistakes
- Wrong or missing namespace. Every VMAP element lives in the
http://www.iab.net/videosuite/vmapnamespace. Documents that drop thevmap:prefix or declare a different URI fail strict parsers. - Invalid
timeOffsetformats.10:00is not a valid timestamp (00:10:00is), and percentages need the%sign. A malformed offset usually means the break silently never fires. allowMultipleAds="false"with a podded response. The VAST behind the tag returns a pod, the player is forbidden to play it, and the break under-fills.- Broken VAST inside
VASTAdData. Inline VAST is still VAST: every rule that applies to a fetched document applies to the embedded one. - No break-level
errortracker. When an ad source fails, the break vanishes without a trace unless the error event is wired.
Does vastlint validate VMAP?
Yes. The vastlint core validates VMAP documents alongside VAST and DAAST: namespace and structure, timeOffset formats, breakType values, AdSource contents, and the embedded VAST inside VASTAdData. See the announcement post on VMAP and DAAST validation in vastlint core and the rules reference.