> For the complete documentation index, see [llms.txt](https://help.craftsmanplus.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.craftsmanplus.com/play/layers/components/iframe.md).

# iFrame

The iFrame component lets you embed external web content directly inside your playable ad. Use it to display hosted web pages, embed third-party widgets, play YouTube videos, load interactive HTML creatives, or nest another playable ad within your scene. The embedded content renders live in both the editor preview and the final exported playable.

### Adding an iFrame

To add an iFrame, click on the **Component** option in the creat assete menu, or click on the **`C`** hotkey. A new iFrame layer will appear on the artboard as a placeholder box. Once added, you can move, resize, and position it like any other layer.

In the editor, the iFrame displays a live thumbnail preview of the loaded content. The preview is non-interactive in edit mode — you'll see what the content looks like, but you won't be able to click or scroll within it until you open the Preview or generate a share link.

***

### Content Formats

The iFrame supports four content formats. Open the **Format** dropdown in the Component section of the properties panel to choose between them.

#### URL

Paste any publicly accessible web address into the URL input field. The iFrame will load that page directly. This is the simplest option and works well for embedding web pages, hosted playables, or any content that has its own URL.

{% hint style="warning" %}
**Note:** The iFrame source must be a **public, self-hosted URL**. Privately hosted or access-restricted content will not render correctly. *Self-hosing may cost money per impression.*
{% endhint %}

#### Embed Snippet

Paste an HTML embed snippet — the kind you'd copy from YouTube, Spotify, Google Maps, or a similar service. PLAY parses the snippet, extracts the embedded content, and renders it inside the iFrame. **The snippet must contain valid HTML**; PLAY validates for proper closing tags and enforces a 10,000 character limit to prevent oversized payloads.

#### HTML

Load a single self-contained `.html` file from your asset library. Click the **Select** button to open the asset library, filtered to HTML files. Pick an HTML file you've already uploaded, or upload a new one from your computer inside the library, then confirm. PLAY hosts the file on the CRAFTSMAN+ server and automatically populates the iFrame with the resulting URL.

Once selected, the selector displays the file name, file type and file size. Click **Replace** to pick a different file from the library, or **Delete** to remove it.

#### ZIP

Load a `.zip` file containing an `index.html` file and its supporting assets (CSS, JavaScript, images). Click **Select** to open the asset library, filtered to ZIP files, and pick a ZIP, or upload a new one inside the library. PLAY extracts the ZIP and serves the content from the CRAFTSMAN+ server. The zip must contain a root-level `index.html` — archives without one will be rejected with the warning "Zip missing index.html file."

**Upload notes for HTML and ZIP:**

* Files are capped at **25 MB**. Anything larger is rejected with "File exceeds 25 MB limit."
* Files you upload stay in your asset library, so you can reuse them in other iFrames without uploading again.
* Click **Delete** next to the file name to remove an upload and return to the empty selector. The file stays in your asset library.

***

### Layer Order Behavior

The iFrame **always renders on top** of your project. You cannot layer other elements over the iFrame, so plan your layout accordingly. If you need elements to appear above the iFrame visually, consider restructuring your scene so the iFrame doesn't overlap with those elements, or resizing the iFrame to allow other elements to sit adjacent to it.

***

### Third-Party Calls Warning

If the iFrame content relies on unrestricted internet access — such as loading external APIs, fetching remote assets, or calling third-party services at runtime — a yellow warning appears in the Component section:

> **Third-Party Calls Required** — This Feature relies on unrestricted internet access which is not supported by all partners.

Some ad networks sandbox playable ads and block outbound network requests. If you see this warning, test your creative on your target partners before going live to confirm the embedded content loads correctly.

***

### Disable iFrame Interactions

By default, the iFrame captures all touch and click events — meaning users interact with the embedded content directly (scrolling a web page, tapping a YouTube play button, etc.). When this is the case, PLAY's native layer interactions (tap, drag, swipe, etc.) are **not available** on the iFrame layer because the embedded content consumes those events first.

Toggle **Disable iFrame Interactions** on to reverse this behavior. When enabled, the iFrame becomes non-interactive, and touch and click events pass through to elements **behind** the iFrame — such as buttons or other creative components on the canvas. This also allows you to assign PLAY's standard interactions to the iFrame layer itself. Use this when the iFrame is decorative, when you want a tap-to-CTA overlay, or anytime the project's own interactivity should take priority over the embedded content.

***

### Animations

The iFrame layer supports Enter, Idle, Event, and Exit animations, configured the same way as any other layer. See the [Animations](/play/properties/animations.md) documentation for details.

***

### Tips and Best Practices

* **Use URL for hosted playables** — if you're embedding another playable ad or a hosted game, paste the direct URL. This is the most reliable approach.
* **Use Embed Snippet for third-party widgets** — YouTube, Google Maps, and similar services provide embed codes designed for iFrame embedding.
* **Use HTML or ZIP for self-contained creatives** — standalone HTML5 mini-games or rich media units that don't need external hosting. Use HTML for single files, ZIP for multi-file projects.
* **Plan around Layer Order** — the iFrame always renders on top. You can't layer other creative elements over it, so position your layout accordingly.
* **Watch for the third-party calls warning** — if you see it, your creative may not work on all ad networks. Test on your target partners first.
* **Mind the interactions toggle** — if users are interacting with the embedded content instead of your playable's CTA, enable **Disable iFrame Interactions**.
* **Always test in Preview and Share Link** — the editor shows a thumbnail, but some content behaves differently when fully interactive.
* **YouTube embed URLs** — use the `/embed/` format (e.g., `https://www.youtube.com/embed/VIDEO_ID`) rather than standard watch URLs, which may not load in all export contexts.

***

### iFrame SDK

You can optionally include the CRAFTSMAN+ iFrame SDK to enable deeper connectivity between your embedded content and the parent playable. This is useful when your iFrame contains a custom game, interactive widget, or rich media experience that needs to control scenes, report progress, or interact with the playable's timeline.

Include this script in your HTML inside the `<head>` tag:

```html
<script src="https://sdk.craftsmanplusserve.com/cmp-sdk.min.js"></script>
```

Your code can then use the `CMP` functions below.

#### `CMP.startNextScene(sceneName, skipExitAnimations?)`

Navigate to a specific scene by name.

* `sceneName` (string) — the name of the scene to navigate to.
* `skipExitAnimations` (boolean, optional) — whether to skip exit animations during the scene transition.

```js
// Navigate to a scene with exit animations
CMP.startNextScene('intro');

// Navigate to a scene without exit animations
CMP.startNextScene('main-menu', true);

// Navigate to a random scene from a list
const scenes = ['Win', 'Lose'];
const pick = scenes[Math.floor(Math.random() * scenes.length)];
CMP.startNextScene(pick);
```

#### `CMP.launchURL(urlToOpen?)`

Open a CTA URL. The player picks the link for the current device: iOS, Android or desktop. A platform you leave out uses the playable's Autofill URL for that platform, then the other platforms' links, and finally `https://craftsmanplus.com`. The click is reported as a CTA click and counts as one engagement, attributed to the iFrame layer, so don't also call `CMP.trackButtonClick()` or `CMP.trackCTAClick()` for it.

* `urlToOpen` (string or object, optional) — a string opens that URL on every device; an object gives a URL per platform with any of `iosUrl`, `androidUrl` and `onClickUrl` (desktop); omit it to open the Autofill URL.

`CMP.launchAppStore()` is deprecated and does the same as `CMP.launchURL()` with no argument.

```js
// One URL for every device
CMP.launchURL('https://example.com');

// A URL per platform; desktop falls back to the Autofill URL
CMP.launchURL({
  iosUrl: 'https://apps.apple.com/app/id123',
  androidUrl: 'https://play.google.com/store/apps/details?id=com.example',
});
```

#### `CMP.playScene()`

Start or resume playback of the current scene.

```js
CMP.playScene();
```

#### `CMP.pauseScene()`

Pause playback of the current scene.

```js
CMP.pauseScene();
```

#### `CMP.skipToTime(time)`

Skip to a specific time in the current scene.

* `time` (number) — the time in seconds to skip to.

```js
// Skip to 5 seconds into the scene
CMP.skipToTime(5);

// Skip forward 10 seconds from the current position
CMP.skipToTime(await CMP.getTime() + 10);
```

#### `CMP.showLayer(layerName)`

Show a layer by name.

* `layerName` (string) — the name of the layer to show.

```js
CMP.showLayer('UI overlay');
```

#### `CMP.hideLayer(layerName)`

Hide a layer by name.

* `layerName` (string) — the name of the layer to hide.

```js
CMP.hideLayer('UI overlay');
```

#### `CMP.getTime()`

Get the current time of the scene in seconds. Returns a `Promise<number>`.

```js
const currentTime = await CMP.getTime();
console.log(`Current time: ${currentTime} seconds`);
```

#### `CMP.updateProgression(type, value)`

Update a playable progression value. PLAY uses this to drive the **Progress Bar** and **Progress Text** components if they are linked to the iFrame.

* `type` (`'score'` | `'moves'` | `'health'`) — the type of progression to update.
* `value` (number) — the value to set.

```js
// Update player score
CMP.updateProgression('score', 1500);

// Update remaining moves
CMP.updateProgression('moves', 10);

// Update player health
CMP.updateProgression('health', 75);
```

#### `CMP.trackButtonClick(eventExtra?)`

Fire a `button_click` analytics event to report an engagement inside your iFrame content. The event counts towards the playable's engagement metrics, including **Engagements by Layer**.

Browser security prevents PLAY from detecting interactions inside iFrame content it doesn't control, so call this function from your own click handlers to make those interactions visible in analytics.

* `eventExtra` (string, optional) — the name reported with the event. Defaults to the iFrame layer's name.

```js
// Report a click, attributed to the iFrame layer's name
CMP.trackButtonClick();

// Report a click under a custom name
CMP.trackButtonClick('spotify-play-button');
```

#### `CMP.trackCTAClick(eventExtra?)`

Fire a `cta_click` analytics event to report a CTA click inside your iFrame content. The click also counts as one engagement, so don't call `CMP.trackButtonClick()` for the same click — that would count it twice. This only reports analytics — to actually open a CTA, use `CMP.launchURL()` instead, which reports the click itself. If your code calls `CMP.trackCTAClick()` and then opens the CTA within a second, it still counts as one engagement.

* `eventExtra` (string, optional) — the name reported with the event. Defaults to the iFrame layer's name.

```js
// Report a CTA click, attributed to the iFrame layer's name
CMP.trackCTAClick();

// Report a CTA click under a custom name
CMP.trackCTAClick('subscribe-banner');
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.craftsmanplus.com/play/layers/components/iframe.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
