> ## Documentation Index
> Fetch the complete documentation index at: https://docs.penbox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Iframe Embedding

> Embed Penbox forms directly in your website using iframes

Penbox allows you to embed forms directly in your website using iframes, so your users never leave your application.

## How It Works

Instead of redirecting users to a Penbox URL, you load the form inside an `<iframe>` on your own page. Penbox communicates back to your page using `postMessage` events.

<Steps>
  <Step title="Configure allowed origins">
    In your workspace **Branding** settings, add the origins (domains) that are allowed to embed Penbox forms. For example: `https://app.yourcompany.com`.
  </Step>

  <Step title="Enable embedding per form">
    On each form template, toggle **Embedding** on in the settings panel. Only forms with this toggle enabled can be embedded.
  </Step>

  <Step title="Add the iframe to your page">
    Use the form URL with the `embed_v=1` query parameter:

    ```html theme={null}
    <iframe
      src="https://fill.penbox.io/f/<flow-customization-id>?embed_v=1&autostart"
      allow="clipboard-write; camera; microphone"
      style="width: 100%; height: 600px; border: none;"
    ></iframe>
    ```
  </Step>

  <Step title="Listen for events">
    Listen for `postMessage` events from the iframe to know when the form is ready and when it's completed.
  </Step>
</Steps>

## Configuration

Embedding requires a **double opt-in**:

1. **Workspace level**: declare which origins are allowed to embed forms (in **Settings > Branding**)
2. **Form level**: enable embedding on each individual form template

This prevents accidental exposure — a workspace with 50 forms won't have them all embeddable just because one origin is configured.

### Allowed Origins

Origins must be exact matches — no wildcards, no paths, no trailing slashes. HTTPS is required in production. `http://localhost` (with optional port) is allowed for development.

| Valid                        | Invalid                                     |
| ---------------------------- | ------------------------------------------- |
| `https://app.example.com`    | `https://app.example.com/` (trailing slash) |
| `https://portal.example.com` | `https://*.example.com` (wildcard)          |
| `http://localhost:3000`      | `https://app.example.com/path` (path)       |

You can configure up to **20 origins** per workspace.

<Warning>
  If the embedding toggle is on for a form but no origins are configured at
  workspace level, embedding will be blocked.
</Warning>

## Query Parameters

| Parameter   | Description                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| `embed_v=1` | **Required.** Activates embed mode: hides the top navigation bar and enables postMessage events.        |
| `autostart` | **Optional.** Skips the welcome page and starts the form automatically. Recommended for embedded forms. |

A typical embedded URL looks like:

```
https://fill.penbox.io/f/<flow-customization-id>?embed_v=1&autostart
```

## Events

Penbox sends events to the parent window using `postMessage`. All events share the same envelope:

```json theme={null}
{
  "source": "penbox",
  "type": "<event-type>",
  "v": 1
}
```

### `ready`

Sent when the form layout has mounted and is ready to be displayed.

```json theme={null}
{
  "source": "penbox",
  "type": "ready",
  "v": 1
}
```

### `completed`

Sent exactly once when the form is submitted. Includes the `request_id` for reference.

```json theme={null}
{
  "source": "penbox",
  "type": "completed",
  "v": 1,
  "request_id": "<uuid>"
}
```

<Note>
  The `completed` event fires regardless of whether an auto-redirect is
  configured on the form. It is sent before any redirect delay.
</Note>

### `close`

Sent when the user clicks the close action inside the form. Includes the `request_id` for reference.

```json theme={null}
{
  "source": "penbox",
  "type": "close",
  "v": 1,
  "request_id": "<uuid>"
}
```

## Integration Example

```html theme={null}
<iframe
  id="penbox-form"
  src="https://fill.penbox.io/f/abc123?embed_v=1&autostart"
  allow="clipboard-write; camera; microphone"
  style="width: 100%; height: 700px; border: none;"
></iframe>

<script>
  window.addEventListener("message", (event) => {
    // Filter for Penbox events
    if (event.data?.source !== "penbox") return;
    // Recommended: verify origin
    if (event.origin !== "https://fill.penbox.io") return;

    switch (event.data.type) {
      case "ready":
        console.log("Form is ready");
        break;
      case "completed":
        console.log("Form completed:", event.data.request_id);
        // Navigate to a thank-you page, close a modal, etc.
        break;
      case "close":
        console.log("Form closed:", event.data.request_id);
        // Hide the iframe, close a modal, etc.
        break;
    }
  });
</script>
```

## Security

Penbox uses the `Content-Security-Policy: frame-ancestors` header to control which origins can embed forms. When embedding is properly configured, the `X-Frame-Options: DENY` header is replaced with a CSP header listing only your allowed origins.

If a page tries to embed a form from an unauthorized origin, the browser blocks it — the iframe will show an error or remain blank.

<Tip>
  Always filter incoming `postMessage` events by checking `event.data?.source   === 'penbox'` and `event.origin === 'https://fill.penbox.io'` to avoid
  processing unrelated messages.
</Tip>

## Known Limitations

* **Exact origins only** — wildcards and regex patterns are not supported. Each origin must be declared explicitly.
* **20 origins max** — contact support if you need more.
* **Token visible to parent** — the parent page can read the iframe URL. This is standard iframe behavior and does not expose additional data.
* **Third-party cookie restrictions** — Safari ITP and Chrome strict mode may block third-party cookies. Penbox uses sessionStorage-based authentication, so this is generally not an issue.
* **Custom form domains** — embedding currently only works with `fill.penbox.io` URLs. Custom domain support is planned.

## Next Steps

<CardGroup cols={2}>
  <Card title="Form Security" icon="lock" href="/forms/security">
    Protect forms with access controls
  </Card>

  <Card title="Form Templates" icon="pen-ruler" href="/forms/form-templates">
    Build and configure form templates
  </Card>
</CardGroup>
