Creative Optimization with Type-Safe Ad Templates
We generate 10,000 ad creative variants per campaign using TypeScript's type system to guarantee every variant is valid.
A programmatic campaign for a regional e-commerce client launches with 4 headline variants, 6 product images, 3 call-to-action buttons, 5 color schemes, and 2 layout formats. That is 720 possible creative combinations. Most of them are wrong.
The red headline on the red background is unreadable. The long headline overflows the compact layout. The "Buy Now" CTA paired with the brand awareness image sends a conflicting message. The product image with a white background disappears into the light color scheme.
Traditional dynamic creative optimization platforms handle this by generating all combinations and then filtering invalid ones at runtime. Some use human review. Some use heuristic rules that catch obvious failures and miss subtle ones. All of them waste money serving broken creatives before the optimizer has enough data to suppress them.
We took a different approach. We made invalid creative combinations a compile error.
The Problem with Dynamic Creative Optimization
DCO is the industry's answer to creative fatigue. Instead of designing one static banner, you provide a set of components (headlines, images, CTAs, backgrounds, layouts) and the system assembles them into thousands of variants, then uses multi-armed bandit algorithms to find the best performers.
The theory is sound. The implementation across the industry is terrible.
We audited the creative output of three major DCO platforms for a single campaign with 1,200 possible combinations. The findings:
- 14% of combinations had readability issues (insufficient contrast between text and background)
- 8% of combinations had layout overflow (text exceeding container bounds on at least one target device)
- 6% of combinations violated the advertiser's brand guidelines (wrong font pairing, unauthorized color combination)
- 3% of combinations had semantic conflicts (promotional CTA on informational content)
That is 31% of combinations that should never have been served. At the campaign's daily spend of $2,400, roughly $744 per day was spent on creatives that were broken, off-brand, or nonsensical. The optimizer would eventually learn to suppress these, but "eventually" means after burning through enough impressions to gather statistically significant performance data. For a creative with a 0.02% CTR because the text is invisible, you need approximately 15,000 impressions before the optimizer confidently suppresses it. At $8 CPM, that is $120 wasted per bad variant. Multiply by 370 bad variants and the waste is staggering.
Type-Safe Creative Templates
Our creative system uses TypeScript as the template definition language. Not TypeScript the runtime. TypeScript the type system. The creative constraints are encoded as types, and invalid combinations fail type checking at build time.
Here is a simplified version of how it works.
Every creative element has a branded type that carries its visual properties:
type Headline = {
readonly _brand: "headline";
text: string;
length: "short" | "medium" | "long";
color: HexColor;
weight: "regular" | "bold";
};
type Background = {
readonly _brand: "background";
color: HexColor;
luminance: "light" | "dark";
};
type Layout = {
readonly _brand: "layout";
format: "compact" | "standard" | "expanded";
maxHeadlineLength: "short" | "medium" | "long";
};
The constraint engine uses conditional types to enforce rules at the type level:
type ValidTextOnBackground<
T extends { color: HexColor },
B extends Background
> = ContrastRatio<T["color"], B["color"]> extends true ? T : never;
type ValidHeadlineInLayout<
H extends Headline,
L extends Layout
> = H["length"] extends FitsIn<L["maxHeadlineLength"]> ? H : never;
A creative variant is a tuple of elements that must satisfy all constraints simultaneously:
type CreativeVariant<
H extends Headline,
I extends ProductImage,
C extends CallToAction,
B extends Background,
L extends Layout
> = ValidTextOnBackground<H, B> extends never
? never
: ValidTextOnBackground<C, B> extends never
? never
: ValidHeadlineInLayout<H, L> extends never
? never
: ValidCtaForIntent<C, I> extends never
? never
: {
headline: H;
image: I;
cta: C;
background: B;
layout: L;
};
When a campaign manager defines creative components in our dashboard (built in TypeScript, naturally), the system generates a TypeScript file containing all element definitions and runs the type checker. Any variant that resolves to never is an invalid combination. The type checker catches it. No runtime check. No human review. No wasted impressions.
The Constraint Library
We maintain a library of approximately 80 creative constraints, organized into categories:
Visual constraints enforce readability. Minimum contrast ratios between text and backgrounds follow WCAG AA standards (4.5:1 for normal text, 3:1 for large text). We compute contrast ratios at the type level using a lookup table encoded as a mapped type over our supported color palette. This sounds absurd. It works.
Layout constraints prevent overflow. Each layout format specifies maximum dimensions for each element slot. Headlines, CTAs, and descriptions have measured text lengths per font and size. A "long" headline in a "compact" layout is a type error because we know, at compile time, that the text will not fit.
Brand constraints encode advertiser guidelines. A luxury brand might specify that bold headlines cannot pair with promotional CTAs. A financial services client might require disclaimer text on any creative containing rate information. These rules are expressed as conditional types that the campaign manager never sees. They just see that certain combinations are unavailable in the dashboard.
Semantic constraints catch message conflicts. An image tagged as "brand awareness" cannot pair with a "Buy Now" CTA. An image tagged as "product showcase" requires a CTA that references the product. These are discriminated unions:
type ImageIntent = "brand_awareness" | "product_showcase" | "lifestyle";
type CtaIntent = "learn_more" | "buy_now" | "explore";
type ValidCtaPairing = {
brand_awareness: "learn_more" | "explore";
product_showcase: "buy_now" | "explore";
lifestyle: "learn_more" | "explore";
};
The Rendering Pipeline
Valid creative variants are rendered into final ad units. We do not use iframes. We do not use JavaScript for static display creatives. Each creative variant is pre-rendered to a self-contained HTML/CSS document at build time.
The rendering pipeline:
- TypeScript type checker validates all variants (compile time)
- Valid variants are passed to our rendering engine
- Each variant is rendered to HTML/CSS using a minimal template engine
- The output is a single
<div>with inline styles, no external dependencies - Static creatives are pre-rendered and cached at edge nodes
- Dynamic creatives (those requiring real-time data like pricing) are rendered on-demand
On-demand rendering completes in under 16ms on mobile devices. The rendering engine is deliberately minimal: no virtual DOM, no component framework, no CSS-in-JS runtime. It is template string concatenation with sanitization. The output is small. A typical display creative is 3-8KB of HTML/CSS, including Base64-encoded icons.
For video and rich media creatives, we use a separate pipeline that is not type-checked in the same way. Video creative optimization is a different problem with different constraints. We are working on it.
The Variant Generation Pipeline
For a campaign with N headline variants, M images, P CTAs, Q backgrounds, and R layouts, the naive approach generates N * M * P * Q * R combinations and type-checks all of them. For large campaigns, this is computationally expensive at build time.
We optimize this with a constraint propagation step. Before generating the full cross-product, we run pairwise compatibility checks. If headline H3 is incompatible with background B2 (insufficient contrast), we prune all variants containing that pair. This reduces the candidate set dramatically. A campaign with 720 naive combinations typically has 180-350 valid variants after constraint propagation.
The type-checking step for 350 variants completes in 2.4 seconds on a single core. This happens once, at campaign launch or when components are modified. It is not on any hot path.
Production Results
We have been running this system in production for seven months across 340 campaigns. The comparison is against our previous approach, which used runtime validation with a rules engine.
Invalid creative rate: 0%. By construction. If the type checker passes, the creative is valid. We have not served a single broken creative since deploying this system. The previous runtime approach had a 2.1% invalid creative rate that we caught through automated visual QA after serving.
CTR improvement: 34% versus traditional A/B testing. This is the headline number, but it requires context. The improvement comes from two sources. First, the optimizer starts with a higher-quality variant pool. Every variant in the pool is valid, on-brand, and readable. The optimizer does not waste exploration budget on broken variants. Second, we generate significantly more variants per campaign (average 280 versus 45 with manual A/B testing), giving the optimizer a larger search space of good options.
Campaign launch time: 4 hours to 35 minutes. Under the old process, a creative team would design variants, a QA team would review them, and invalid combinations would be flagged for redesign. The cycle took 4 hours minimum. Now the campaign manager uploads components, the type checker validates in seconds, and the campaign launches with all valid variants immediately.
Creative waste: $0. No impressions are spent on creatives that do not meet brand guidelines, readability standards, or layout requirements. Over 340 campaigns, we estimate this has saved approximately $890,000 in wasted ad spend compared to the industry-average invalid creative rate of 6-8%.
The Uncomfortable Implication
The ad-tech industry treats creative optimization as a machine learning problem: generate variants, serve them, measure performance, optimize. We treat it as a type-checking problem: generate only valid variants, then optimize within the valid set.
The machine learning approach tolerates waste because the optimizer will eventually suppress bad variants. But "eventually" is expensive when every impression costs money. The type-safe approach eliminates an entire category of waste by making it structurally impossible.
TypeScript's type system is Turing-complete. We are using it to enforce advertising brand guidelines at compile time. It is one of the most productive abuses of a type system we have ever committed, and it saves our clients real money every day.