Skip to content

Creative Build Pipeline

How a creative goes from JSON data to a deployable ad tag.

Overview

Frontend saves creative data (JSON)
         |
Backend receives & stores creative.json
         |
Backend dispatches build job
         |
Creative-Composer orchestrates:
  1. Copy engine files to temp workbench
  2. Remap creative data (logicSettings → operators)
  3. Create build profile
  4. Run fileposer (inject creative data into engine)
  5. Vite build (engine + data → JS bundle)
  6. injectTag (post-process stub.js with engine code)
  7. Generate tag.html
         |
Output: built/ directory with deployable files

Build output structure

creatives/{id}/{cuid}/
  creative.json          # The creative data as stored
  log.txt               # Build log with timestamps
  tag.html              # Production embed tag (loads stub.js)
  tag-vast.html         # VAST version for video
  built/
    index.html           # Dev/preview HTML (unused in production)
    live.html            # Live preview template
    assets/
      creative-engine.js  # Vite bundle: engine code + creative data
      stub.js             # Tag stub: creates iframe, injects engine

Key files in the pipeline

stub.ts (Creative-Engine)

Template at Creative-Engine/templates/tagstub/stub.ts. Contains placeholders:

PlaceholderReplaced with
CREATIVEIDPLACEHOLDERCreative ID number
CREATIVE_GROUP_ID_PLACEHOLDERCreative group ID
CAMPAIGN_ID_PLACEHOLDERCampaign ID
BRAND_ID_PLACEHOLDERBrand ID
CREATIVESCRIPTSPLACEHOLDERJSON-stringified engine JS bundle
CREATIVETYPEPLACEHOLDER'banner' or 'expandable'
BANNERWIDTHPLACEHOLDERWidth (px or undefined for fullscreen)
BANNERHEIGHTPLACEHOLDERHeight (px or undefined for fullscreen)

Creates an iframe with srcdoc. The engine code runs inside this iframe:

javascript
iframe.srcdoc = `
  <html>
    <body>
      <div id='creative-${creativeId}'></div>
      <script async>
        const stubConf = ${JSON.stringify(this.stubConf)}
        ${creativeScripts}  // <-- The entire engine bundle runs here
      </script>
    </body>
  </html>
`

injectTag.ts (Creative-Composer)

Post-build processor at Creative-Composer/src/bin/commands/job/injectTag.ts:

  1. Reads the built stub.js from vite output
  2. Minifies it with UglifyJS
  3. Reads creative-engine.js and JSON.stringifies it
  4. Replaces all placeholders with actual values
  5. Splices the engine code into CREATIVESCRIPTSPLACEHOLDER
  6. Writes the final stub.js back

Known fragility: The engine JS file is found via assets[0] — the first file returned by directory listing. If ANY static CSS file exists in the assets directory (e.g. from a <style> block in a Vue component), it will sort before creative-engine.js alphabetically and be picked up instead, breaking the build silently.

tag.html (generated by Backend)

Simple HTML wrapper that loads stub.js:

html
<script data-creative-id="{id}">
(function() {
  var s = document.createElement('script');
  s.src = '{baseUrl}/built/assets/stub.js?creativeInTesting=true&bust='+Date.now();
  s.async = true;
  document.head.appendChild(s);
})();
</script>

Preview modes

Builder preview (LocalBuildPreview.vue)

Uses postMessage to send creative data directly to the engine running in an iframe. No build step required — changes are live.

Standalone preview (PreviewIframe.vue)

Triggers a full backend build, then loads the tag.html in an iframe. This exercises the complete build pipeline. The URL pattern is:

{backend}/assets/creatives/{id}/{cuid}/tag.html

Vite build configuration

The engine's vite.config.ts defines two entry points:

javascript
rollupOptions: {
  input: {
    'creative-engine': path.resolve(__dirname, entryFile),
    'stub': path.resolve(__dirname, 'templates/tagstub/stub.ts'),
  },
}

A custom wrap-in-iife plugin wraps stub.js in an IIFE after build.

Troubleshooting

about:srcdoc:20 Uncaught SyntaxError: Unexpected token '{'

Symptom: Standalone preview fails. Console shows syntax error on line 20 of about:srcdoc.

Cause: A <style> block in an engine Vue component generated a .css file in the build output. injectTag.ts uses assets[0] which picked up the CSS file instead of creative-engine.js. The CSS content was injected into stub.js as JavaScript, causing a syntax error.

How to verify:

bash
# Check if a CSS file exists in the build output
ls built/assets/
# If you see creative-engine-*.css alongside creative-engine.js, that's the problem

# Check stub.js size — should be 200KB+, not ~5KB
wc -c built/assets/stub.js

# A 5KB stub.js means the engine code was NOT injected

Fix: Find and remove the <style> block from the engine component. Move styles into the component's styles() computed property. See engine-styling-system.md.

Build succeeds but preview shows blank/broken creative

Check the build log: log.txt in the creative's build directory shows timing and any errors.

Check creative.json: The stored creative data is saved alongside the build. Compare it against what the frontend sends.

Rebuild: The standalone preview uses the last successful build. If you changed engine code, you need to trigger a new build from the frontend.

How to find a specific build

Build outputs are stored at:

Application-Backend/tmp/creatives/assets/creatives/{creativeId}/{cuid}/

Sort by modification time to find the most recent build for a creative. Each creative may have multiple builds (different cuids).

Important constraints

  1. No <style> blocks in engine components — generates CSS files that break injectTag.ts
  2. All styling via styles() computed — see engine-styling-system.md
  3. assets[0] fragility — the Composer assumes only JS files in the assets directory
  4. Form blocks bypass the remapper — they pass through createPayload/getCreativeObject untouched as creativeBlocks, unlike operators which go through logicSettings

Internal documentation