<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aleksander Sekowski</title>
    <description>The latest articles on DEV Community by Aleksander Sekowski (@aleksuix).</description>
    <link>https://dev.to/aleksuix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866734%2F559a0a69-3c28-49e2-91c7-503810b941ba.png</url>
      <title>DEV Community: Aleksander Sekowski</title>
      <link>https://dev.to/aleksuix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aleksuix"/>
    <language>en</language>
    <item>
      <title>I Built a VS Code Extension That Validates VAST XML as You Type</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Mon, 20 Apr 2026 04:13:48 +0000</pubDate>
      <link>https://dev.to/aleksuix/i-built-a-vs-code-extension-that-validates-vast-xml-as-you-type-2ln</link>
      <guid>https://dev.to/aleksuix/i-built-a-vs-code-extension-that-validates-vast-xml-as-you-type-2ln</guid>
      <description>&lt;p&gt;If you work in ad tech you've probably seen VAST XML. It's the IAB standard that carries every video and CTV ad impression: the tag that tells a player what to show, where to fire tracking pixels, and how to handle wrappers. $30B+ in annual US ad spend runs through it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw258f958f7g4cw5ps19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw258f958f7g4cw5ps19.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also surprisingly easy to ship a broken one. The spec is six versions deep (2.0 through 4.3), touches several other standards (XML, RFC 3986, IANA media types, ISO 4217), and the tooling for validating it has historically been: read the PDF, or find out from your partner's discrepancy report.&lt;/p&gt;

&lt;p&gt;So I built vastlint, an open-source VAST linter with 108 rules written in Rust. Last week I shipped the VS Code extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Install it from the VS Code Marketplace. Open any .xml file that contains a VAST tag.&lt;/p&gt;

&lt;p&gt;You get squiggles on the exact element or attribute that violates the spec, hover tooltips with the rule ID and the fix, and Problems panel integration with file path and line number. VAST version is auto-detected. No config, no CLI, no external service. The validator runs entirely in-process via WebAssembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually catches
&lt;/h2&gt;

&lt;p&gt;Here's a broken tag in the editor. This one is missing  on the  element, a required field since VAST 2.0 that's easy to drop when templating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Linear&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Duration is required — VAST-2.0-linear-duration --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MediaFiles&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MediaFile&lt;/span&gt; &lt;span class="na"&gt;delivery=&lt;/span&gt;&lt;span class="s"&gt;"progressive"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[https://example.com/video.mp4]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/MediaFile&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/MediaFiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Linear&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The squiggle lands on . Hovering shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAST-2.0-linear-duration · Error
&amp;lt;Linear&amp;gt; is missing required &amp;lt;Duration&amp;gt;
IAB VAST 2.0 §3.7.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other rules are less obvious than a missing required element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VAST-2.0-mediafile-https: HTTP  URL on HTTPS inventory (mixed-content, ad won't play)&lt;/li&gt;
&lt;li&gt;VAST-2.0-duration-format:  is 00:30 instead of 00:00:30 (strict players reject this, others misfire tracking events at the wrong times)&lt;/li&gt;
&lt;li&gt;VAST-4.1-mezzanine-recommended: no  on a 4.1 tag (SSAI platforms reject these for CTV)&lt;/li&gt;
&lt;li&gt;VAST-4.1-vpaid-apiframework: VPAID apiFramework in a 4.1 tag (deprecated, Roku and Fire TV skip it)&lt;/li&gt;
&lt;li&gt;VAST-2.0-duplicate-impression: same impression URL appears twice (both sides double-count, neither report is technically wrong, they just never match)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core library is vastlint-core on crates.io, a pure Rust library with no external dependencies. The VS Code extension loads it as a WebAssembly module so validation runs locally with no network calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string in -&amp;gt; parse XML -&amp;gt; detect VAST version -&amp;gt; run rule set -&amp;gt; issues out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical validation time on a real-world CTV tag is under 100 microseconds. The WASM overhead adds a bit in the editor context but it's imperceptible while typing.&lt;/p&gt;

&lt;p&gt;The same core powers a CLI (cargo install vastlint), a REST API on RapidAPI, an MCP server for Claude/Cursor/Windsurf, and the online validator at vastlint.org.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-rule configuration
&lt;/h2&gt;

&lt;p&gt;Not every rule applies to every context. Mezzanine is noise for browser-only inventory but critical for Roku. You can tune per-rule in settings.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vastlint.rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-4.1-mezzanine-recommended"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-2.0-mediafile-https"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-4.1-vpaid-apiframework"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid levels: "error", "warning", "info", "off".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I ran into this problem at work. The available tooling was either proprietary, behind a paywall, or only checked structure against the XSD. The spec is public. The failure modes are documented. There was no good reason an open-source implementation didn't exist.&lt;/p&gt;

&lt;p&gt;The other thing: these errors get caught way too late. A malformed tag makes it through QA, gets sent to the partner, fails on their platform, and shows up three weeks later in a discrepancy report. The campaign has already run, the impressions are gone. Catching it in the editor before the file leaves your machine is the right fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=aleksUIX.vastlint" rel="noopener noreferrer"&gt;Install from VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vastlint.org/validate" rel="noopener noreferrer"&gt;Online validator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>adtech</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>The 10 VAST Errors That Silently Kill Your CTV Ad Revenue</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Wed, 08 Apr 2026 01:35:58 +0000</pubDate>
      <link>https://dev.to/aleksuix/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue-25i8</link>
      <guid>https://dev.to/aleksuix/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue-25i8</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@aleksander-sekowski/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue" rel="noopener noreferrer"&gt;Originally published on Medium.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Samsung TV Plus just crossed 100 million monthly active users. Netflix ended 2024 with 277.6 million subscribers worldwide, 70 million on its ad-supported tier. Amazon Prime Video had 230 million users. Roku reported 89.8 million active accounts in Q4 2024. The ad spend flowing through all of it: $33 billion in the US alone in 2024 runs on VAST XML.&lt;/p&gt;

&lt;p&gt;Every impression, every pixel, every billing event depends on a tag being parsed correctly by players, ad servers, and measurement vendors that were built by different teams to different tolerances. Penthera found that 40% of VOD ads fail. Google Ad Manager flags any gap larger than 25% between impressions served and impressions counted as a sign of systemic VAST errors. Most of these failures trace back to a handful of tag-level mistakes that are trivially detectable before the ad ever reaches a player.&lt;/p&gt;

&lt;p&gt;I built an open-source tool that catches all errors mentioned below: vastlint. 108 rules across VAST 2.0 through 4.3. Free, no account, runs in CI in about 100 microseconds per tag.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aleksUIX" rel="noopener noreferrer"&gt;
        aleksUIX
      &lt;/a&gt; / &lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;
        vastlint
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;vastlint&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://crates.io/crates/vastlint-cli" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2beb5b5855b57656861b67861574aecb4cee782d97520ddfa281e7698b6b629d/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f766173746c696e742d636c692e7376673f6c6162656c3d6372617465732e696f" alt="crates.io"&gt;&lt;/a&gt;
&lt;a href="https://crates.io/crates/vastlint-core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a72af7d00fdcded39a6a5c8dff3c947cabcea697d93e6ca79d588d146ccdae79/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f766173746c696e742d636f72652e7376673f6c6162656c3d766173746c696e742d636f7265" alt="crates.io"&gt;&lt;/a&gt;
&lt;a href="https://docs.rs/vastlint-core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2e66f1a456a606735a90cc9cc47c2b60e78d103706a464beed69a6b0912e8b93/68747470733a2f2f646f63732e72732f766173746c696e742d636f72652f62616467652e737667" alt="docs.rs"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/vastlint" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f57064e4b17dcbd17fc10fce20ffadc141f9da635abec86d173144a8c552d152/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f766173746c696e742e7376673f6c6162656c3d6e706d" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://github.com/aleksUIX/vastlint/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/25a5325411ddfdd46094f1e6a698dac67891eaf213c5891a8a6c76f3466091fe/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f6c2f766173746c696e742d636c692e737667" alt="license"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A VAST XML validator. Checks ad tags against the IAB Tech Lab VAST specification so you don't have to read it. Over $30 billion in annual CTV and video ad spend flows through VAST XML, and malformed tags are one of the most common causes of lost impressions, broken tracking, and revenue discrepancies between platforms. There is no widely adopted open-source tool that validates VAST XML against the full IAB specification across all published versions.&lt;/p&gt;
&lt;p&gt;Validates VAST documents against:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://iabtechlab.com/standards/vast/" rel="nofollow noopener noreferrer"&gt;IAB Tech Lab VAST&lt;/a&gt; 2.0, 3.0, 4.0, 4.1, 4.2, and 4.3 -- structural rules derived from the published XSD schemas and spec prose&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/xml/" rel="nofollow noopener noreferrer"&gt;W3C XML 1.0&lt;/a&gt; well-formedness (malformed documents are rejected before any spec rule runs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc3986" rel="nofollow noopener noreferrer"&gt;RFC 3986&lt;/a&gt; URI syntax (all URL fields)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iana.org/assignments/media-types/" rel="nofollow noopener noreferrer"&gt;IANA Media Types&lt;/a&gt; (MediaFile and resource MIME types)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iso.org/iso-4217-currency-codes.html" rel="nofollow noopener noreferrer"&gt;ISO 4217&lt;/a&gt; currency codes (Pricing elements)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ad-id.org/" rel="nofollow noopener noreferrer"&gt;Ad-ID&lt;/a&gt; registry format (UniversalAdId)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;108 rules across required fields, schema validation, structural correctness…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here are the ten I see most often.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Missing  attributes&lt;br&gt;
delivery, type, width, height — all four are required. Leave one out and the player guesses. CTV devices don't guess well. A tag that plays fine in Chrome fails on Roku because the device won't infer the delivery method from context. The XML is valid. The spec is violated. The blank slot is yours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP media URLs on HTTPS inventory&lt;br&gt;
A  pointing at http:// gets blocked by mixed-content policies on any HTTPS page or app. The ad doesn't play, the impression doesn't fire, fill rate drops. The tag is valid XML. It's technically spec-compliant. It just won't work on most real inventory. This one is invisible to any validator that only checks structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bad  format&lt;br&gt;
The spec requires HH:MM:SS or HH:MM:SS.mmm. Tags show up with 00:30, 30s, plain 30, or empty. Strict players reject the creative outright. Loose players guess wrong and fire progress events — firstQuartile, midpoint, complete — at the wrong times. Your reporting looks fine but the measurement is off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No  element&lt;br&gt;
No  URL means the ad plays but nobody gets paid. The DSP doesn't know it rendered, the SSP can't bill. Both sides pull discrepancy reports and argue. On wrapper chains it's worse. A missing impression at any level means that link in the transaction goes unrecorded. The outer wrapper fires, the inner wrapper doesn't, the numbers never reconcile.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrapper without &lt;br&gt;
A  with no redirect URL is a dead end. The slot goes unfilled, no error fires, the demand partner reports zero delivery. Usually happens when a template renders a wrapper variant but the URL variable is null or empty. At scale, that's thousands of lost impressions per minute with nothing in the logs pointing at the cause.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt; missing or malformed&lt;br&gt;
Required on every  since VAST 4.0. Needs an idRegistry attribute and a non-empty value. Without it, frequency capping and competitive separation break. The ad server can't tell two creatives apart, so it can't enforce exclusion rules between competing brands or cap a user who's already seen the ad five times. The ads still serve. The contracts you're supposed to be honoring don't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VPAID apiFramework on VAST 4.1+ tags&lt;br&gt;
VPAID was deprecated in VAST 4.1. Google, Amazon, and most major SSPs block VPAID execution entirely. Tags declaring apiFramework="VPAID" pass XML validation. They may even pass some linting tools that only check structure. They get filtered before reaching a screen. Fill rate craters and the root cause is a single attribute value that no one thought to check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tracking event typos&lt;br&gt;
The spec defines a fixed set: creativeView, start, firstQuartile, midpoint, thirdQuartile, complete, and so on. Misspell one — Midpoint, mid_point, MidPoint — and no player fires it. The pixel silently never calls. VAST 4.0 also removed fullscreen and exitFullscreen. Tags copied from 3.0 templates still carry those events. They worked. They don't anymore.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Malformed XML&lt;br&gt;
Unescaped &amp;amp;, unclosed elements, bad UTF-8. Common when tags are assembled by string concatenation instead of an XML library. Many ad servers have lenient parsers that silently fix this. CTV devices don't. You debug a blank slot for an afternoon and the problem is &amp;amp; instead of &amp;amp; in a click URL buried three wrappers deep.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Version declaration doesn't match content&lt;br&gt;
The tag says  but contains  and , which are 4.x elements. The player applies 2.0 parsing rules and skips what it doesn't recognize. Happens constantly when templates are copied between projects without updating the version attribute. Everything looks fine in dev. In production the player is running a different code path than you think.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of these is detectable before the tag reaches a player. None of them require a live ad server, a real device, or a debugging session.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>rust</category>
      <category>node</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
