Skip to content
Back to home

Embed the Kreativsimulator via <iframe>

Embed the Kreativsimulator into your own website in seconds – and optionally drive it entirely from the outside: your own product picker, your own creatives, your own UI. This guide walks every step with copy-paste code and a live demo to try.

Step 1

Get your domains whitelisted

For security reasons the browser only allows embedding from domains we have whitelisted in advance (Content-Security-Policy: frame-ancestors).

Before embedding, send us all domains the iframe should appear on. We add them to your tenant configuration. Wildcards are supported.

For example:

  • https://www.your-agency.com
  • https://campaign.your-agency.com
  • https://*.your-agency.com (wildcard)

Without whitelisting the iframe stays blank (white area). The live demo below also only loads if the embedding domain is whitelisted – otherwise use the “Open in new tab” link.

Step 2

Embed code

A single <iframe> tag is enough. No API key, no extra configuration on your side – authentication runs entirely server-side.

View code HTML
HTML
<iframe
  src="https://demo.kreativsimulator.com/"
  title="Kreativsimulator"
  style="width: 100%; height: 720px; border: 0;"
  allow="fullscreen"
  loading="lazy"
></iframe>

Embed URL: https://<dein-mandant>.kreativsimulator.com/

The tenant is derived from the subdomain. For the public sandbox use demo.kreativsimulator.com.

Language and start product can optionally be preset via query parameters – see the next section.

Step 3

URL parameters

Optionally append these query parameters to the embed URL (start with ?, chain with &):

ParameterValuesEffectDefault
langde · en · fr · itUI languageTenant → browser → de
productProduct code (ec5472c2)Loads this product directly (deep link). Unknown code → first product.First product
header0 · 1Show/hide the header (logo + product selector)1 (visible)
sidebar0 · 1Show/hide the sidebar (incl. mobile drawer + hamburger)1 (visible)
playback0 · 1Show/hide the playback/transport buttons in the bar1 (visible)
timeline0 · 1Show/hide the timeline (scrubbing bar)1 (visible)
export0 · 1Show/hide the “Export creation” button1 (visible)
effects0 · 1Show/hide the effect dropdown (photo products only) – e.g. for your own picker1 (visible)
fullscreen0 · 1Fullscreen icon: 0=never, 1=always (incl. desktop); omitted = mobile / in fullscreen onlyadaptive
embedminimal · kioskShorthand: hides header, sidebar and export (playback bar stays)
themeURL to a CSS fileLoads a custom theme (CSS variables) that overrides the tenant theme
bgany CSS color, transparent or image URLBackground of the area around the canvas (letterbox/frame), not the videoTheme background
stage<width>x<height> (1280x720)Fixed display pixel size of the canvas instead of fluid widthfluid (100%)
placeholderBgany CSS colorBackground of the placeholder box (loader without a creative) – direct shortcut, no ?theme= fileTheme/default (#ffffff)
placeholderTextany CSS colorColor of the letter inside the placeholder boxTheme/default (#000000)
placeholderBorderany CSS colorBorder color of the placeholder boxTheme/default (#000000)
  • Boolean parameters accept 0/1 (also true/false, yes/no). embed=kiosk is evaluated first – individual parameters override it afterwards (e.g. ?embed=kiosk&header=1 still shows the header).
  • The visibility switches (header/sidebar/playback/timeline/export/effects) can also be toggled at runtime via setChrome (see API). Playback, export and effect selection always work via postMessage – regardless of whether the matching controls are visible. So you can build a fully custom UI (your own effect picker via setEffect, hide the built-in dropdown with effects=0).
  • theme CSS: loaded as an extra <link> after the tenant theme, overriding its CSS variables (e.g. --vc-accent, --vc-bg). CSS cannot run JavaScript; the file may be cross-origin (no CORS needed). Values for ?theme=/?bg= must be URL-encoded (e.g. %23 for #).
  • bg=transparent: the parent page shows through the area around the canvas (best with ?embed=kiosk). The video canvas itself always stays opaque – transparent only affects the frame/letterbox edges.
  • Placeholder colors directly in the URL (placeholderBg/placeholderText/placeholderBorder): brands just the placeholder box (the generated letter motif shown until a creative is set) – a shortcut with no ?theme= file. Accepts any CSS color (hex %23ff5a10, rgb(...), hsl(...), names like white) and overrides the tenant theme and ?theme=; only the params you set override. Tip: don't set placeholderBg equal to placeholderText (invisible letter). For more branding (accent, buttons, spinner …) keep using ?theme=.

Examples

View code URL
URL
https://demo.kreativsimulator.com/                          # default
https://demo.kreativsimulator.com/?lang=en                  # English UI
https://demo.kreativsimulator.com/?product=ec5472c2         # open a product
https://demo.kreativsimulator.com/?product=ec5472c2&lang=fr # language + product
https://demo.kreativsimulator.com/?embed=kiosk              # canvas + playback bar only
https://demo.kreativsimulator.com/?header=0&sidebar=0       # header + sidebar off
https://demo.kreativsimulator.com/?timeline=0&playback=0    # timeline + transport off
https://demo.kreativsimulator.com/?effects=0                # effect dropdown off (own picker)
https://demo.kreativsimulator.com/?fullscreen=1             # fullscreen icon also on desktop
https://demo.kreativsimulator.com/?stage=1280x720           # fixed 1280×720 canvas
https://demo.kreativsimulator.com/?bg=%23000000             # black letterbox area
https://demo.kreativsimulator.com/?embed=kiosk&bg=transparent  # parent page shows through
https://demo.kreativsimulator.com/?theme=https%3A%2F%2Fcdn.kunde.de%2Fks-theme.css

# Placeholder colors directly (no theme file) — encode # as %23:
https://demo.kreativsimulator.com/?placeholderBg=%23ff5a10&placeholderText=%23ffffff&placeholderBorder=%23ffffff
# … or with rgb()/color names (no %23 needed):
https://demo.kreativsimulator.com/?placeholderBg=rgb(255,90,16)&placeholderText=white&placeholderBorder=white
Step 4

Embed responsively

In embed mode (chrome off, e.g. ?embed=kiosk or ?sidebar=0) the canvas fills the iframe edge-to-edge and stays centered – no left offset, no 1280px cap. So just drop the iframe with width:100%; height:100% into any sized parent container (flexbox/grid too). If its aspect ratio differs from 16:9 you get a symmetric letterbox (color via ?bg=).

Recommended recipe – the iframe fills its flex/grid container:

View code HTML
HTML
<!-- Any parent container; the iframe fills it completely. -->
<div style="display:flex; width:100%; height:480px;">
  <iframe
    src="https://demo.kreativsimulator.com/?embed=kiosk"
    title="Kreativsimulator"
    style="flex:1; border:0;"
    allow="fullscreen"
    loading="lazy"
  ></iframe>
</div>

For an exact aspect ratio without letterboxing, give the wrapper aspect-ratio: 16 / 9:

View code HTML
HTML
<!-- Exact 16:9, no letterbox: constrain the wrapper. -->
<div style="aspect-ratio: 16 / 9; width: 100%;">
  <iframe
    src="https://demo.kreativsimulator.com/?embed=kiosk"
    title="Kreativsimulator"
    style="width: 100%; height: 100%; border: 0;"
    allow="fullscreen"
    loading="lazy"
  ></iframe>
</div>

With the sidebar visible (standalone view without ?embed/?sidebar=0) the canvas stays left-aligned (max 1280px) – the fill/center logic deliberately applies only in embed mode.

Step 5 · Core use case

Just the canvas – drive everything yourself

The most common case: show only the WebGL canvas and build the entire UI yourself. Hide header, sidebar and buttons – via URL with ?embed=kiosk (header + sidebar + export off, playback bar stays) or selectively with ?header=0&sidebar=0&playback=0&export=0. At runtime the same works via setChrome.

The key point: playback and export always work via postMessage – regardless of whether the built-in buttons are visible. So your own buttons drive the iframe; for a custom progress bar, listen to the playbackState event.

Handy without the sidebar: when the sidebar is hidden, clicking a placeholder directly on the canvas opens the file dialog for exactly that placeholder – so end users can upload a creative even without your own upload UI.

embed=kiosk hides the built-in chrome – your buttons send play/pause/seek/export. Try it in the live demo below with the “Chrome” toggles and the custom playback bar.

Canvas in kiosk mode + your own playback controls, kept in sync via the playbackState event:

View code HTML + JS
HTML + JS
<!-- Canvas only — your own playback UI drives it from the outside -->
<iframe id="ks" src="https://demo.kreativsimulator.com/?embed=kiosk"
        style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>

<button id="play">▶︎</button>
<button id="pause">⏸</button>
<button id="stop">⏹</button>
<button id="back">⏮</button>
<button id="fwd">⏭</button>
<input id="scrub" type="range" min="0" value="0" />
<button id="export">Export</button>
<button id="fs">⛶ Fullscreen</button>

<script>
  const iframe = document.getElementById("ks");
  const send = (msg) => iframe.contentWindow.postMessage(msg, "*");
  const P = "kreativsimulator:";

  play.onclick  = () => send({ type: P + "play" });
  pause.onclick = () => send({ type: P + "pause" });
  stop.onclick  = () => send({ type: P + "stop" });
  back.onclick  = () => send({ type: P + "stepBackward" });
  fwd.onclick   = () => send({ type: P + "stepForward" });
  scrub.oninput = (e) => send({ type: P + "seek", frame: Number(e.target.value) });
  // Direct export at 1280×720 (no dialog):
  export.onclick = () =>
    send({ type: P + "export", filename: "creation", width: 1280, height: 720 });
  // Fullscreen ON reliably via requestFullscreen on the iframe (the user gesture
  // is here, on the click). OFF/toggle could also go via the postMessage event.
  fs.onclick = () => {
    if (document.fullscreenElement) document.exitFullscreen();
    else iframe.requestFullscreen();   // iframe needs allow="fullscreen"
  };

  window.addEventListener("message", (e) => {
    const m = e.data;
    if (!m || m.source !== "kreativsimulator") return;
    if (m.type === "playbackState") {
      scrub.max = m.frameCount - 1;
      if (document.activeElement !== scrub) scrub.value = m.frame; // don't fight the drag
      play.disabled = m.playing;
      pause.disabled = !m.playing;
    }
    if (m.type === "fullscreenChanged") {
      fs.textContent = m.fullscreen ? "⛶ Exit fullscreen" : "⛶ Fullscreen";
    }
  });

  // Ask for the current state once (e.g. after load):
  // send({ type: P + "getState" });
</script>
Step 6 · Custom design

Your own design via CSS (theme & bg)

The iframe adopts your branding through CSS variables. ?theme= loads a custom CSS file after the tenant theme and overrides its variables (e.g. --vc-accent, --vc-bg). ?bg= sets the letterbox area around the canvas (color or image URL), ?stage= a fixed canvas pixel size.

Embed with theme + background

View code HTML
HTML
<!-- Your own theme + a black letterbox around the canvas -->
<iframe
  src="https://demo.kreativsimulator.com/?embed=kiosk&theme=https%3A%2F%2Fcdn.kunde.de%2Fks-theme.css&bg=%23000000"
  title="Kreativsimulator"
  style="width: 100%; height: 720px; border: 0;"
  allow="fullscreen"
  loading="lazy"
></iframe>

Theme template

Download template ks-theme.css
View template kreativsimulator-theme-template.css
kreativsimulator-theme-template.css
/* =============================================================================
   Kreativsimulator – Theme-Vorlage (CSS Custom Properties)
   =============================================================================

   So passt du das Aussehen des eingebetteten Kreativsimulators an deine Marke an.

   VERWENDUNG
   ----------
   1. Diese Datei kopieren, Werte unten anpassen, auf einem öffentlich
      erreichbaren HTTPS-URL ablegen (z. B. https://cdn.deine-agentur.de/ks-theme.css).
   2. Den URL per `?theme=` an die iframe-Einbettung hängen – URL-kodiert:

      <iframe src="https://demo.kreativsimulator.com/?theme=https%3A%2F%2Fcdn.deine-agentur.de%2Fks-theme.css"
              style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>

   HINWEISE
   --------
   • Die Datei wird als zusätzliches <link> NACH dem Mandanten-Theme geladen und
     überschreibt dessen Werte (CSS-Kaskade). Cross-Origin ist erlaubt (kein CORS
     nötig) – CSS kann kein JavaScript ausführen.
   • Du musst NICHT alle Variablen setzen. Lösche einfach die Zeilen, die du nicht
     ändern willst – dann gilt der jeweilige Standard des Simulators/Mandanten.
   • Farben: jede gültige CSS-Farbe (#hex, rgb(), hsl(), benannt).
   • Den Hintergrund UM den Canvas (Letterbox) steuerst du besser per
     `?bg=<farbe|transparent|url>` – nicht über `--vc-bg` (das ist auch der
     Canvas-Hintergrund während des Ladens).
   ========================================================================== */

:root {
  /* ── Meta ──────────────────────────────────────────────────────────────
     Hell-/Dunkel-Schema. Beeinflusst u. a. native Formular-Elemente.
     Werte: dark | light */
  --vc-color-scheme: dark;

  /* Schriftfamilie der gesamten Oberfläche.
     Eigene Webfont? Oben in der Datei per @import laden, z. B.:
       @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
     und hier referenzieren: 'Inter', system-ui, sans-serif */
  --vc-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* ── Flächen ───────────────────────────────────────────────────────────
     Grundfarben der UI-Container (Sidebar, Dialoge, Karten). */
  --vc-bg: #0a0a0a;             /* App-Hintergrund / Canvas-Hintergrund beim Laden */
  --vc-surface: #181818;       /* Sidebar, Dialog-Flächen */
  --vc-surface-raised: #2a2a2a;/* erhöhte Elemente (Tracks, Chips, Inputs) */
  --vc-surface-hover: #3a3a3a; /* Hover-Fläche */
  --vc-border: #2a2a2a;        /* Standard-Rahmenfarbe */
  --vc-border-strong: #444444; /* betonte Rahmen */

  /* ── Text ──────────────────────────────────────────────────────────────*/
  --vc-text: #eeeeee;          /* Standard-Textfarbe */
  --vc-text-muted: #888888;    /* sekundärer Text */
  --vc-text-subtle: #666666;   /* dezente Hinweise */
  --vc-text-on-accent: #ffffff;/* Text auf Akzent-/Primärflächen */

  /* ── Akzent (Marke) ────────────────────────────────────────────────────
     Wird u. a. für aktive Zustände, Timeline/Abspielkopf und Slider-Punkte
     verwendet. `--vc-accent-rgb` MUSS dieselbe Farbe als "R, G, B" enthalten
     (ohne rgb()-Klammern) – wird für halbtransparente Hervorhebungen genutzt. */
  --vc-accent: #df3b00;
  --vc-accent-strong: #ff5722; /* kräftigere Akzentvariante (Hover) */
  --vc-accent-rgb: 223, 59, 0; /* = --vc-accent als R, G, B */
  --vc-accent-muted: #3a3a3a;  /* gedämpfter Akzent-Hintergrund */

  /* ── Header (Kopfzeile mit Logo + Produktauswahl) ──────────────────────
     Nur relevant, wenn der Header sichtbar ist (sonst per ?header=0 ausblenden). */
  --vc-header-bg: transparent;     /* Hintergrund der Kopfzeile */
  --vc-header-text: #eeeeee;       /* Titel-/Header-Text */
  --vc-header-text-muted: #888888; /* sekundärer Header-Text */
  --vc-header-border: #1a1a1a;     /* untere Trennlinie */
  --vc-header-border-width: 1px;   /* Stärke der Trennlinie (0px = keine) */
  --vc-header-padding: 12px 20px;  /* Innenabstand der Kopfzeile */
  --vc-logo-height: 26px;          /* Höhe des Mandanten-Logos */

  /* ── Buttons ───────────────────────────────────────────────────────────*/
  --vc-button-bg: #2a2a2a;       /* Button-Fläche */
  --vc-button-bg-hover: #3a3a3a; /* Button-Fläche bei Hover */
  --vc-button-text: inherit;     /* Button-Textfarbe */
  --vc-button-border: #444444;   /* Button-Rahmen */
  --vc-button-radius: 4px;       /* Eckenradius der Buttons */

  /* ── Dialoge (Export-Dialog „Export als MP4") ──────────────────────────
     Ohne Override folgen sie den Surface-/Border-Variablen. */
  --vc-dialog-bg: var(--vc-surface);        /* Dialog-Fläche */
  --vc-dialog-border: var(--vc-border);     /* Dialog-Rahmen */
  --vc-dialog-radius: 8px;                  /* Eckenradius des Dialogs */
  --vc-dialog-backdrop: rgba(0, 0, 0, 0.6); /* Abdunklung hinter dem Dialog */

  /* ── Eingabefelder & Selects ───────────────────────────────────────────
     Gilt für alle Input-/Select-Felder – auch das Effekt-Dropdown bei
     Foto-Produkten. Ohne Override folgen sie den Surface-/Text-Variablen. */
  --vc-input-bg: var(--vc-surface-raised);    /* Feld-Hintergrund */
  --vc-input-border: var(--vc-border-strong); /* Feld-Rahmen */
  --vc-input-text: var(--vc-text);            /* Eingabetext */
  --vc-input-radius: 4px;                     /* Eckenradius der Felder */
  --vc-label-text: var(--vc-text-muted);      /* Feld-Beschriftungen */

  /* ── Slider / Zeitleiste ───────────────────────────────────────────────
     Der Abspielkopf und die In/Out-Marken nutzen --vc-accent (s. o.). */
  --vc-slider-track: #3a3a3a;    /* Track-Farbe der Regler */

  /* ── Lade-Spinner (Ladeanzeige beim Initialisieren / Footage-Laden) ────
     Ohne Override folgt der Spinner automatisch --vc-accent (Bogen) bzw.
     --vc-border (Ring) – diese Zeilen also nur setzen, wenn du ihn abweichend
     gestalten willst. */
  --vc-spinner-color: var(--vc-accent); /* rotierender Bogen */
  --vc-spinner-track: var(--vc-border); /* ruhender Ring */
  --vc-spinner-size: 48px;              /* Durchmesser */

  /* ── Vorschau-Canvas (Rahmen um das Video) ─────────────────────────────*/
  --vc-canvas-outline-color: #2a2a2a; /* Outline-Farbe des Canvas */
  --vc-canvas-outline-width: 1px;     /* Outline-Stärke (0px = kein Rahmen) */
  --vc-canvas-radius: 0px;            /* Eckenradius des Canvas – z. B. 16px für
                                         runde Ecken (rundet Video, Lade-Fläche
                                         und Rahmen einheitlich). 0px = eckig. */

  /* ── Viewport-Rahmen (optionaler Rahmen um die ganze Fläche) ───────────*/
  --vc-frame-color: transparent; /* Farbe des Außenrahmens */
  --vc-frame-width: 0px;          /* Stärke (0px = aus) */

  /* ── Platzhalter (Plakatflächen ohne hochgeladenes Motiv) ──────────────
     Die generierte „A/B"-Platzhaltertextur. */
  --vc-placeholder-bg: #ffffff;     /* Hintergrund der Platzhalterfläche */
  --vc-placeholder-text: #000000;   /* Buchstabe/Beschriftung */
  --vc-placeholder-border: #000000; /* Rahmen der Platzhalterfläche */
}

/* =============================================================================
   BEISPIEL: helles Marken-Theme (zum Vergleich auskommentiert).
   Zum Ausprobieren das obige :root entfernen und dieses aktivieren.
   ============================================================================*/
/*
:root {
  --vc-color-scheme: light;
  --vc-bg: #ffffff;
  --vc-surface: #ffffff;
  --vc-surface-raised: #f2f2f2;
  --vc-surface-hover: #e8e8e8;
  --vc-border: #e2e2e2;
  --vc-border-strong: #c2c2c2;
  --vc-text: #1a1a22;
  --vc-text-muted: #5e5e68;
  --vc-text-subtle: #8c8c94;
  --vc-text-on-accent: #ffffff;
  --vc-accent: #0066ff;
  --vc-accent-strong: #0047b3;
  --vc-accent-rgb: 0, 102, 255;
  --vc-accent-muted: #d6e4ff;
  --vc-header-bg: #ffffff;
  --vc-header-text: #1a1a22;
  --vc-header-text-muted: #6a6a74;
  --vc-header-border: #e2e2e2;
  --vc-header-border-width: 1px;
  --vc-button-bg: #0066ff;
  --vc-button-bg-hover: #0047b3;
  --vc-button-text: #ffffff;
  --vc-button-border: #0066ff;
  --vc-button-radius: 6px;
  --vc-slider-track: #e0e0e0;
  --vc-canvas-outline-color: #0066ff;
  --vc-canvas-outline-width: 2px;
}
*/
  • No CORS needed: the CSS file is loaded as a <link> (not via fetch like creatives) and may be cross-origin. CSS cannot run JavaScript.
  • URL-encode: values for ?theme=/?bg= must be URL-encoded (e.g. %23 for #, %2F for /).
  • The loading spinner is themeable too: --vc-spinner-color (rotating arc), --vc-spinner-track (ring) and --vc-spinner-size (diameter) – without an override it follows --vc-accent / --vc-border automatically.
  • Rounded canvas corners: --vc-canvas-radius (default 0px) rounds the preview canvas – video, loading area and frame alike – e.g. --vc-canvas-radius: 16px; in the ?theme= CSS.
  • Dialogs & input fields are themeable too: the export dialog (--vc-dialog-bg, --vc-dialog-border, --vc-dialog-radius, --vc-dialog-backdrop) and all input/select fields incl. the photo-effect dropdown (--vc-input-bg, --vc-input-border, --vc-input-text, --vc-input-radius, --vc-label-text) can be styled individually – without an override they follow the surface/text variables.
  • Only placeholder colors? You don't need a theme file for that: ?placeholderBg=/?placeholderText=/?placeholderBorder= set the placeholder box directly via URL (see URL parameters above) and override the theme + ?theme=.
  • You'll find the full list of overridable CSS variables in the theme template below.
Step 7 · Interactive

Try remote control live

Here you see the real iframe (left) next to a completely custom control UI (right) – your own product images, your own creatives, your own buttons. Every click sends a postMessage to the iframe. This is exactly how you rebuild it on your site; the matching code is right below.

Real iframe from demo.kreativsimulator.com Open in new tab

Show / hide chrome

Hide header/sidebar/buttons live → setChrome. “Canvas only” hides everything.

Your playback bar

Works even without visible buttons. Scrubber via seek, synced via playbackState.

Your product picker

Your page builds these cards from the ready event. Click → setProduct. Photo products show their effect count via getEffects (pre-fetched, no product switch).

Your creatives

Your own placeholder creatives. Click → setCreative (loads the image via CORS).

Your own file per placeholder

Drag an image onto A or B → setCreative with loader. In production you upload the file to your CORS host and pass the URL; here it's a data URL for the demo.

Export

Resolution from ready.render.presets. Click → export (MP4).

Load variants (URL parameters)

Reloads the iframe with parameters – this is how embed/bg/stage take effect.

Placeholder colors (URL)

Brands just the placeholder box directly via URL – no ?theme= file. “Apply” reloads the iframe with ?placeholderBg/Text/Border.

Language

Event log (iframe → your page)

  • No events received yet …

The following code drives exactly this live demo. Use it as your starting point:

View code HTML + JS
HTML + JS
<iframe id="ks" src="https://demo.kreativsimulator.com/"
        style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>

<script>
  const iframe = document.getElementById("ks");
  const send = (msg) => iframe.contentWindow.postMessage(msg, "*");

  window.addEventListener("message", (e) => {
    const m = e.data;
    if (!m || m.source !== "kreativsimulator") return;

    if (m.type === "ready") {
      // Build your own buttons from the product list
      for (const p of m.products) {
        const btn = document.createElement("button");
        btn.textContent = p.name;
        btn.onclick = () =>
          send({ type: "kreativsimulator:setProduct", code: p.code });
        document.body.appendChild(btn);
      }
      // Build your own resolution picker + export button from m.render.presets
      const sel = document.createElement("select");
      for (const r of m.render.presets) {
        const o = document.createElement("option");
        o.value = r.width + "x" + r.height;
        o.textContent = r.label;
        sel.appendChild(o);
      }
      const exportBtn = document.createElement("button");
      exportBtn.textContent = "Export";
      exportBtn.onclick = () => {
        const [width, height] = sel.value.split("x").map(Number);
        send({ type: "kreativsimulator:export", filename: "creation", width, height });
      };
      document.body.append(sel, exportBtn);
    }

    // The product is active now → only here may you target a placeholder.
    if (m.type === "productChanged") {
      const first = m.loaders[0];                // take the name from the event
      if (first) {
        send({
          type: "kreativsimulator:setCreative",
          url: "https://cdn.your-agency.com/creative.png",
          loader: first.name,                    // this placeholder only
        });
      }
    }

    if (m.type === "error") console.warn("KS error:", m.message);
  });

  // Creative on ALL placeholders (no loader) — fine right after `ready`:
  // send({ type: "kreativsimulator:setCreative", url: "https://cdn.your-agency.com/creative.png" });
</script>
Step 8 · Reference

postMessage API

Build your own UI and drive the iframe via window.postMessage. The iframe also sends events back (including the list of available product codes).

Incoming commands (your page → iframe)

MessageEffect
{ type: "kreativsimulator:setProduct", code: "ec5472c2" }Loads the product with this code.
{ type: "kreativsimulator:setCreative", url: "https://…/creative.png" }Places the image/video as the creative on all placeholders.
{ type: "kreativsimulator:setCreative", url: "…", loader: "Loader A" }Fills only the named placeholder.
{ type: "kreativsimulator:clearCreative", loader?: "Loader A" }Resets one (or without loader all) placeholders to the default.
{ type: "kreativsimulator:setLanguage", lang: "fr" }Switches the UI language (de/en/fr/it).
{ type: "kreativsimulator:setEffect", id: "static-5s" }Photo products: selects an entry from ready.effects – a static preset (static-5s/static-10s, always present) or a named Ken-Burns effect. id: null = static still (1 frame). Changes the timeline length → a playbackState follows.
{ type: "kreativsimulator:getEffects", code: "ec5472c2" }Queries the effect list of any product without activating it (live view stays unchanged) → reply as an effects event. For a per-product effect picker before calling setProduct.
{ type: "kreativsimulator:play" } · pause · stopStarts / pauses / stops (back to start) playback.
{ type: "kreativsimulator:stepForward", frames?: 1 } · stepBackwardSteps frames frames forward/back (default 1) and pauses.
{ type: "kreativsimulator:seek", frame: 120 }Jumps to an absolute frame index (0…frameCount-1).
{ type: "kreativsimulator:export", filename?, width?, height? }Starts the MP4 export. With width+height directly at any resolution; without, the export dialog opens.
{ type: "kreativsimulator:setChrome", header?, sidebar?, playback?, timeline?, export?, effects? }Toggles individual UI areas at runtime (each true/false). effects = effect dropdown (photo products).
{ type: "kreativsimulator:fullscreen", enabled? }Fullscreen: true=on, false=off, without enabled=toggle (see note).
{ type: "kreativsimulator:getState" }Requests the current playback state → reply as playbackState.

Outgoing events (iframe → your page)

View code JS
JS
{ source: "kreativsimulator", v: 1, type: "ready",
  product: "ec5472c2", locale: "de",
  products: [{ code: "ec5472c2", name: "City-Light …" }, …],
  loaders: [{ name: "Loader A", aspectRatio: 2.96 }, …],
  // native render resolution (16:9) + suggested export presets
  render: { width: 1280, height: 720, aspectRatio: 1.7778,
            presets: [{ label: "E-Mail / WhatsApp (640×360)", width: 640, height: 360 },
                      { label: "Standard HD (1280×720)", width: 1280, height: 720 },
                      { label: "YouTube / Full HD (1920×1080)", width: 1920, height: 1080 }] },
  // photo products (productType "bild"): effects[] = two always-present static
  // presets (static-5s, static-10s) + named Ken-Burns effects; default = static-5s.
  // video products report isPhoto:false, effects:[], selectedEffect:null.
  isPhoto: true,
  effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
            { id: "static-10s", name: "Static (10s)", durationFrames: 250 },
            { id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
  selectedEffect: "static-5s" }

{ source: "kreativsimulator", v: 1, type: "productChanged", code: "ec5472c2",
  loaders: [{ name: "Loader A", aspectRatio: 2.96 }, …],
  isPhoto: true,
  effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
            { id: "static-10s", name: "Static (10s)", durationFrames: 250 },
            { id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
  selectedEffect: "static-5s" }

// reply to getEffects {code}: a product's effect list WITHOUT activating it.
// same fields as productChanged (incl. the static presets), plus code.
// video products report isPhoto:false, effects:[], selectedEffect:null.
{ source: "kreativsimulator", v: 1, type: "effects", code: "ec5472c2",
  isPhoto: true,
  effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
            { id: "static-10s", name: "Static (10s)", durationFrames: 250 },
            { id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
  selectedEffect: "static-5s" }

// playback state: on play/pause, ~4×/s while playing, after seek/stop/step, and on getState
// for photo products, frameCount equals the active effect's duration
{ source: "kreativsimulator", v: 1, type: "playbackState",
  frame: 120, playing: true, frameCount: 250, fps: 25 }

// confirmation after setChrome, with the new visibility state
{ source: "kreativsimulator", v: 1, type: "chromeChanged",
  header: false, sidebar: false, playback: true, timeline: true, export: false, effects: false }

// fullscreen state — on every change (e.g. ESC); for syncing your own button
{ source: "kreativsimulator", v: 1, type: "fullscreenChanged", fullscreen: true }

{ source: "kreativsimulator", v: 1, type: "error", message: "…", code, loader }

Always filter incoming messages on source === "kreativsimulator". v is the protocol version (currently 1).

Complete example

Build your own buttons from the product list, place a creative on a targeted placeholder (correct ordering), and a custom resolution picker from render.presets:

View code HTML + JS
HTML + JS
<iframe id="ks" src="https://demo.kreativsimulator.com/"
        style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>

<script>
  const iframe = document.getElementById("ks");
  const send = (msg) => iframe.contentWindow.postMessage(msg, "*");

  window.addEventListener("message", (e) => {
    const m = e.data;
    if (!m || m.source !== "kreativsimulator") return;

    if (m.type === "ready") {
      // Build your own buttons from the product list
      for (const p of m.products) {
        const btn = document.createElement("button");
        btn.textContent = p.name;
        btn.onclick = () =>
          send({ type: "kreativsimulator:setProduct", code: p.code });
        document.body.appendChild(btn);
      }
      // Build your own resolution picker + export button from m.render.presets
      const sel = document.createElement("select");
      for (const r of m.render.presets) {
        const o = document.createElement("option");
        o.value = r.width + "x" + r.height;
        o.textContent = r.label;
        sel.appendChild(o);
      }
      const exportBtn = document.createElement("button");
      exportBtn.textContent = "Export";
      exportBtn.onclick = () => {
        const [width, height] = sel.value.split("x").map(Number);
        send({ type: "kreativsimulator:export", filename: "creation", width, height });
      };
      document.body.append(sel, exportBtn);
    }

    // The product is active now → only here may you target a placeholder.
    if (m.type === "productChanged") {
      const first = m.loaders[0];                // take the name from the event
      if (first) {
        send({
          type: "kreativsimulator:setCreative",
          url: "https://cdn.your-agency.com/creative.png",
          loader: first.name,                    // this placeholder only
        });
      }
    }

    if (m.type === "error") console.warn("KS error:", m.message);
  });

  // Creative on ALL placeholders (no loader) — fine right after `ready`:
  // send({ type: "kreativsimulator:setCreative", url: "https://cdn.your-agency.com/creative.png" });
</script>

Important

  • Ordering on product change: after setProduct the new product is only active once the productChanged event arrives. Send placeholder-targeted commands (setCreative/clearCreative with loader) only afterwards – before that the iframe only knows the old product's placeholders. A product change also resets all creatives.
  • Your own controls: playback (play/pause/stop/step…/seek) and export always work – whether or not the built-in buttons are visible. Hide them with ?embed=kiosk and drive everything yourself.
  • Pick the export resolution: export with width+height renders at any resolution. Native resolution (1280×720, 16:9) and presets come from ready.render – keep the 16:9 aspect ratio or the image distorts.
  • Photo products & Ken-Burns effects: some products are still images with named camera moves. ready/productChanged then report isPhoto: true and a list of effects ({ id, name, durationFrames }). The list always starts with two static presets (static-5s = 5 s, static-10s = 10 s), followed by the named effects; on load static-5s is selected automatically (selectedEffect). Use setEffect { id } to switch entry (or id: null for a pure still image, 1 frame). Playback and export work just like video; playbackState.frameCount equals the active entry's duration.
  • Pre-fetch effects (without switching product): ready/productChanged only carry the effects of the active product. To build an effect picker for other products without switching the live view, send getEffects { code } and listen for the effects event (same fields as productChanged, plus code). That gives you each effect's id/name to drive later via setEffect (after setProduct to that code).
  • Turn fullscreen on reliably: browsers only allow enabling it as a direct reaction to a user gesture – a fullscreen postMessage usually lacks that and may be blocked. So call iframe.requestFullscreen() on your page on click (the iframe needs allow="fullscreen"); the postMessage is fine for off/toggle. The state comes via fullscreenChanged.
  • Placeholder names are not free-form – use exactly one name from the loaders[] array of ready or productChanged.
  • CORS: on setCreative the iframe fetches the image/video URL. The host of your file must therefore send Access-Control-Allow-Origin, otherwise an error event comes back.
  • Security: which pages may embed the iframe is set server-side via frame-ancestors – only those can send messages at all.

Good to know

  • No API key needed – authentication runs entirely server-side.
  • Users can operate the simulator directly in the iframe or you drive it via postMessage from your page.
  • The iframe loads video footage – a stable connection is recommended.

Troubleshooting

The iframe stays blank / white area

Most likely the embedding domain is not whitelisted. The browser console (F12) then shows “Refused to frame … frame-ancestors …”. Send us the exact domain (incl. https:// and any subdomain) and we'll whitelist it.

The iframe shows “403”

The tenant is not yet fully configured server-side (no API key stored). Please contact us.

A creative doesn't appear (error event)

The creative URL sends no CORS, or the loader name doesn't match the active product. Check the image URL's Access-Control-Allow-Origin and send loader-targeted commands only after the productChanged event.

Ready to embed?

Send us your domain(s) – we'll whitelist the embed and you'll be live in minutes.