Three files, not one
The VAST repository merged pull request 57 on 17 July 2026, from a branch called 4.4Development, titled CTV Ad Portfolio Updates. The file everyone looked at was vast_4.4.xsd, the draft schema we wrote about here five days later.
It was not the only file in that pull request. Two others landed alongside it: extensions/ctv_ad_portfolio.md and extensions/ctv_qrcode.md. Neither is a draft. Both define standardised extension payloads for VAST 2.0, and between them they cover the same six formats and the same QR code metadata as the draft schema, through a completely different mechanism.
That is not a redundancy. It is a reach decision, and it is probably the more consequential half of the release.
Why the standards body targeted a spec from 2008
VAST 4.4 is a working draft under discussion. VAST 4.2 and 4.3 are final but unevenly supported. VAST 2.0 is the version that parses essentially everywhere, including on the long tail of CTV devices that will still be in living rooms when 4.4 is finalised.
The problem is that VAST 2.0's NonLinear element has almost nothing in it. Its whole vocabulary is StaticResource, IFrameResource, HTMLResource, TrackingEvents, NonLinearClickThrough and AdParameters. A pause ad delivered through native VAST 2.0 is a JPEG, with no video, no duration, no interactive creative file and therefore no quartile tracking.
The CTV Ad Portfolio extension solves that without touching the schema. It adds an Extension element under InLine/Extensions with type="ctv_ad_portfolio", and puts a MediaFiles container inside it. Video files, image files, SIMID interactive creative files, a Duration, tracking events, icons, click tracking. The delivery model that 4.4 adds natively, back-ported into the version that already ships.
Players that do not recognise the extension ignore it, which is exactly what VAST extensions are for. The document also asks ad servers to include a native NonLinear StaticResource as a fallback where practical, so an unaware player still renders something rather than firing an error.
One release, two encodings of the same signals. Tooling that recognises the 4.x pattern treats the 2.0 pattern as unstructured extension data, and reports nothing at all.
The payload is not the 4.x payload
This is the part that matters for anyone who has already built against the signaling guidance. The 4.x pattern described in the finalised guidance carries one AdCOM signal per Extension element, each marked ext="adcom". The VAST 2.0 extension carries all of them inside a single named container, as plain child elements, alongside the media.
Same four signals, same AdCOM lists, same integers, different XML. A parser looking for Extension@type="plcmt" finds nothing in a 2.0 document, and a parser looking for Extension@type="ctv_ad_portfolio" finds nothing in a 4.x one.
<!-- 4.x pattern: one Extension per AdCOM signal --><Extensions> <Extension type="plcmt" ext="adcom"><plcmt>5</plcmt></Extension> <Extension type="pos" ext="adcom"><pos>7</pos></Extension> <Extension type="playbackmethod" ext="adcom"><playbackmethod>9</playbackmethod></Extension> <Extension type="attr" ext="adcom"><attr>19</attr></Extension></Extensions> <!-- VAST 2.0 extension: one container, signals plus media --><Extensions> <Extension type="ctv_ad_portfolio"> <CreativeId>creative-pause-qr-001</CreativeId> <plcmt>5</plcmt> <pos>7</pos> <playbackmethod>9</playbackmethod> <attr>19</attr> <attr>21</attr> <Duration>00:00:15</Duration> <MediaFiles> <MediaFile delivery="progressive" type="video/mp4" width="1920" height="1080" bitrate="8000" codec="H.264"> <![CDATA[https://cdn.example.com/pause/brand_cine_1080p.mp4]]> </MediaFile> </MediaFiles> </Extension></Extensions>CreativeId is doing load-bearing work
There is a structural problem the extension has to solve and it is worth understanding before you generate one. In VAST 2.0 the Extensions container hangs off InLine, not off Creative. So an extension that describes a specific creative has no inherent binding to it.
The document's answer is a CreativeId child element whose value matches the Creative@id attribute, required whenever the response contains more than one Creative. If it is omitted, the extension applies to the only NonLinearAds creative in the response.
Which means a multi-creative response that omits CreativeId is not a parse error. It is a silent misattribution: the media files, the duration, the format signal and the creative attributes all get applied to whichever creative the receiving platform decides to apply them to. Nothing in the XML is malformed. The wrong ad renders in the wrong slot, or the right ad renders with the wrong declared format, and the tag validates.
The QR extension takes the opposite approach and avoids the problem: it goes under Creative/CreativeExtensions, so it is already scoped to its creative. The document notes that if you do put QR metadata in an InLine-level Extension instead, it should carry a creative identifier, and that Creative/CreativeExtensions is the preferred placement.
The QR extension, and the coordinate system people will get wrong
CreativeExtension type="tl_qrcode" carries four things: QrCodeScanUrl, an optional QrCodeImageUrl, a QrCodePosition with xPosition and yPosition, and a QrCodeSize with size. Positions and size are percentages, and the QR code is assumed square.
The percentages are relative to the ad view, not the screen. The document works the example: an overlay lower third occupying a 1920 by 324 ad view at the bottom of a 1920 by 1080 screen resolves xPosition="10.0%" to 192 pixels within the ad view and yPosition="10.0%" to 32.4 pixels within it, while size="15.0%" resolves to a 288 by 288 square because it is measured against the ad view width. Anyone who assumes screen coordinates puts the code somewhere else entirely, and on a lower third the vertical error is the difference between inside the ad and outside it.
The other thing to internalise is that QrCodeScanUrl is not a clickthrough. The document is explicit that it does not replace ClickThrough or NonLinearClickThrough, that the two may differ, and that a scan destination is often a CTV-specific coupon or attribution endpoint while the clickthrough points at a general landing page. It also asks that measurement distinguish four separate things: the impression, the QR code rendering, the scan, and downstream advertiser-site activity. Presence of a QR code is not exposure, and exposure is not a scan.
Get VAST spec updates, platform guides, and release notes in your inbox.
What vastlint did with these, and what we changed
We shipped seventeen rules for the CTV Ad Portfolio and the 4.4 draft on 25 July, including value checks for plcmt, pos, playbackmethod and the motion attributes, and structural checks for the QR block. Every one of them was written against the 4.x pattern, and the 2.0 path had no coverage at all.
Running the extension documents own examples through that build was not a good look. None of the AdCOM value checks fired, because the integers were not where the rules looked for them. None of the QR checks fired, because the block sits in Creative/CreativeExtensions on a 2.0 document. What did fire was the generic extension check, reporting that a MediaFiles element inside an Extension is in an unusual place, which is true in general and wrong here: in this extension that is precisely where it belongs.
The squeezeback example collected one more, a version mismatch, because it declares version 2.0 while carrying a SIMID interactive creative file. Strictly that is correct, SIMID is a 4.x capability. In context it is the entire point of the extension, which exists to deliver 4.x capability inside a 2.0 document. A rule that is right in general was wrong on this path.
That was a gap and it was ours, so we closed it. The value and QR checks now reach the 2.0 container, the generic extension and version rules skip standardised IAB containers, and six new rules cover the failure modes the container introduces on its own. The catalog goes from 212 rules to 218. All five VAST 2.0 examples published in the two extension documents now validate clean.
One of those six is worth more than the others. VAST 2.0 hangs Extensions off InLine rather than Creative, so a multi-creative response with no CreativeId binds the media and the format signals to whichever creative the receiving platform happens to pick. Nothing else in the document is malformed, which is why it needs a rule of its own rather than a schema check.
While fixing it we found a second defect of our own making. Our attr check accepted only 21, 22 and 23, the three motion attributes. AdCOM 1.0-202607 added 19 Contains advertiser QR Code and 20 Support alpha channel transparency in the same family, and IAB declares 19 in its examples, so we were reporting the reference implementation as wrong. The accepted set is now 19 through 23.
Before After Static Screensaver Ad 2 warnings 0 findings extension-misplaced-element (x2) Video Pause Ad 1 info 0 findings adcom-attr-not-motion on attr 19 SIMID Squeezeback Ad 4 warnings 0 findings extension-misplaced-element (x3) version-mismatch Pause Ad with QR Code 0 findings 0 findings (no checks were reachable) (signals and QR both validated)The values are a live binding to a document that moves
Both extension documents make the same deliberate choice: they do not reproduce the AdCOM enumeration tables. They link to them, and they say that if a referenced AdCOM list is updated, implementations should follow the updated list without requiring a change to the extension document.
As a spec-maintenance decision that is clearly right. It stops four copies of the same table drifting apart. As an operational fact it means the meaning of an integer inside your VAST response is defined by a separate repository on a separate release cadence, and you are expected to track it.
Worth knowing what that repository did this month. AdCOM 1.0-202607 was tagged on 16 July, the day before this pull request merged, and it is the release that actually adds plcmt 5 through 9, pos 8 through 17, playbackmethod 8 through 11, and creative attributes 19 through 23. It also rewords five playback methods that already existed, and it carries a Content object with seven field names that were renamed by accident in March and corrected on the main branch on 22 July without the tag being re-cut.
So the honest summary of the binding is: follow the current list, and be aware that the current list and the current release are not always the same document.
If you are shipping CTV Ad Portfolio formats through VAST 2.0
- Decide which encoding each partner expects, and ask rather than assume. The 4.x per-signal pattern and the 2.0 container pattern are both current and they are not interchangeable.
- Always send CreativeId when the response has more than one Creative. It is the only thing binding the extension to its creative, and omitting it fails silently rather than loudly.
- Include a native NonLinear fallback resource wherever practical, so a player that ignores the extension still has something to render instead of firing an error.
- Treat QR position and size as ad-view relative, and check that the resolved rectangle fits inside the ad view and does not cover an AdChoices icon or a player control.
- Keep QrCodeScanUrl and the clickthrough separate in your reporting, and do not let a rendered QR code be counted as a scan.
- Pin the AdCOM release you validate enum values against, and diff it when a new one is tagged. The integers in your VAST response are defined somewhere you do not control.
Check a CTV Ad Portfolio tag
Paste a tag into the validator for the structural, media file, macro and tracking findings, plus the CTV Ad Portfolio and 4.4 rules on the 4.x signaling path. It runs locally in the browser and nothing is stored.
Open the validatorSources and further reading
The extension document defining Extension type="ctv_ad_portfolio", its CreativeId binding rule, the MediaFiles delivery model and the AdCOM signal elements. Merged 17 July 2026.
The tl_qrcode CreativeExtension: scan URL, image URL, ad-view-relative position and size, and the measurement guidance separating rendering from scanning.
The third file in the same pull request, and the rules we shipped for it.
The finalised signaling guidance and what it asks a VAST response to declare.
What the integers in these extensions actually mean, what changed on 16 July, and why the tagged release and the current document disagree.
Every rule vastlint checks, including the CTV Ad Portfolio and VAST 4.4 rules shipped on 25 July.