---
title: "Embed API Reference"
nav_title: "Embed API"
description: "Embed Perspective interviews programmatically using the SDK, iframe, or JavaScript API."
tags: ["embed conversation", "website widget", "JavaScript SDK", "React SDK", "inline interview", "npm"]
date: "2026-05-08"
nav_order: 5
nav_display: true
---

# Embed API Reference

The Perspective Embed SDK lets you drop interviews into any web page with a script tag and a data attribute. For more control, use the JavaScript API to create and manage embed instances programmatically.

For the user-facing setup guide (choosing embed types, previewing, copying snippets from the dashboard), see [Embed Interviews on Your Website or App](/docs/guide/collect/embed-interviews).

## Quick Start

Add the SDK script and a trigger element to your page:

```html
<div data-perspective-widget="RESEARCH_ID"></div>
<script src="https://getperspective.ai/v1/perspective.js"></script>
```

Replace `RESEARCH_ID` with the ID of your perspective (visible in the dashboard URL or returned by the [Create Perspective API](/docs/build/api-overview#create-a-perspective)).

The script loads asynchronously and initializes all trigger elements on the page automatically.

## Embed Types

Each embed type uses a different data attribute on the trigger element:

| Type | Attribute | Element | Description |
|------|-----------|---------|-------------|
| **Widget** | `data-perspective-widget` | `<div>` | Inline embed that renders directly in the page flow. |
| **Popup** | `data-perspective-popup` | `<button>` | Modal overlay centered on screen. |
| **Slider** | `data-perspective-slider` | `<button>` | Side panel that slides in from the edge. |
| **Float** | `data-perspective-float` | `<div>` | Floating chat bubble anchored to the corner. |
| **Fullpage** | `data-perspective-fullpage` | `<div>` | Full-viewport interview experience. |

Button-based types (Popup, Slider) require a `<button>` element. The user clicks it to open the interview.

### Examples

**Inline widget:**
```html
<div data-perspective-widget="RESEARCH_ID"></div>
```

**Popup triggered by a button:**
```html
<button data-perspective-popup="RESEARCH_ID">
  Share your feedback
</button>
```

**Floating chat bubble:**
```html
<div data-perspective-float="RESEARCH_ID"></div>
```

## URL Parameters

Pass parameters via the `data-perspective-params` attribute as comma-separated `key=value` pairs:

```html
<div
  data-perspective-widget="RESEARCH_ID"
  data-perspective-params="source=homepage,user_id=123,plan=pro"
></div>
```

### Built-In Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `email` | string | Pre-fill participant email. |
| `name` | string | Pre-fill participant name. |
| `returnUrl` | URL | Redirect URL after interview completion. |
| `voice` | `"0"` | Set to `"0"` to disable voice mode (text-only). |
| `scroll` | `"0"` | Set to `"0"` to disable scroll mode. |
| `hideProgress` | `"true"` | Hide the progress bar. |
| `hideGreeting` | `"true"` | Hide the welcome greeting at the start. |
| `hideBranding` | `"true"` | Hide the branding header (logo and company name). |
| `enableFullScreen` | `"true"` / `"false"` | Show or hide the fullscreen button. Defaults to `"true"`. |

Any unrecognized parameters are treated as custom key-value pairs. They are passed through to webhooks and data exports, making them useful for attribution and tracking (e.g., `campaign=summer2024`).

The SDK also forwards search parameters from the parent page URL into the embed iframe, except for SDK-owned parameters such as `embed`, `embed_type`, `theme`, and `brand.*` color keys. Parameters passed explicitly through `data-perspective-params` or the JavaScript `params` option override forwarded parent-page values. For more on how this becomes participant metadata, see [Track Participant Context](/docs/guide/collect/track-participant-context).

## Theming

### Color Mode

Force a color mode with `data-perspective-theme`:

```html
<div
  data-perspective-widget="RESEARCH_ID"
  data-perspective-theme="dark"
></div>
```

Accepted values: `dark`, `light`, `system` (default, follows OS preference).

### Brand Colors

Override brand colors with `data-perspective-brand` (light mode) and `data-perspective-brand-dark` (dark mode):

```html
<div
  data-perspective-widget="RESEARCH_ID"
  data-perspective-brand="primary=#7c3aed,bg=#ffffff"
  data-perspective-brand-dark="primary=#a78bfa,bg=#1a1a1a"
></div>
```

Available color tokens:

| Token | Description |
|-------|-------------|
| `primary` | Primary accent color (buttons, links). |
| `secondary` | Secondary accent color. |
| `bg` | Background color override. |
| `text` | Text color override. |

## Advanced Attributes

Use these attributes when you need more control over SDK behavior:

| Attribute | Applies To | Description |
|-----------|------------|-------------|
| `data-perspective-no-style` | Popup, Slider, Float | Prevents the SDK from applying default button styling to the trigger or launcher. |
| `data-perspective-disable-close` | Popup, Slider | Hides close controls and disables overlay click / Escape close behavior. |
| `data-perspective-launcher-icon` | Float | Sets the launcher icon to `default`, `avatar`, or an image URL. |
| `data-perspective-launcher-style` | Float | Semicolon-separated CSS declarations for the float launcher, such as `width:64px;height:64px;border-radius:50%`. |
| `data-perspective-launcher-class` | Float | Adds CSS classes to the float launcher while keeping SDK classes. |
| `data-perspective-disable-jsonld-attribution` | All SDK embeds | Skips JSON-LD structured data injection. Other SDK attribution signals still remain. |
| `data-perspective-chat` | Float | Legacy alias for `data-perspective-float`. Prefer `data-perspective-float` for new embeds. |

## Auto-Open (Popup Only)

Trigger a popup automatically without a user click:

```html
<div
  data-perspective-popup="RESEARCH_ID"
  data-perspective-auto-open="timeout:5000"
  data-perspective-show-once="session"
  style="display:none"
></div>
```

**Auto-open triggers:**
- `timeout:N` -- Opens the popup after `N` milliseconds.
- `exit-intent` -- Opens when the user moves their cursor toward the browser chrome (desktop only).

**Show-once options:**
- `session` -- Show once per browser session.
- `visitor` -- Show once per visitor (persisted in localStorage).
- `false` -- Show every time.

## JavaScript API

For programmatic control, use the global `Perspective` object:

```javascript
// Create an inline widget
const container = document.querySelector("#perspective-widget");
const widget = Perspective.createWidget(container, {
  researchId: "RESEARCH_ID",
  params: { source: "app", user_id: "123" },
  onReady: () => console.log("Loaded"),
  onSubmit: () => console.log("Completed"),
  onNavigate: (url) => window.location.href = url,
  onClose: () => console.log("Closed"),
  onError: (err) => console.error(err),
});

// Open a popup
const popup = Perspective.openPopup({
  researchId: "RESEARCH_ID",
  onClose: () => console.log("Closed"),
});

// Open a slider
const slider = Perspective.openSlider({
  researchId: "RESEARCH_ID",
});

// Create a floating chat bubble
const float = Perspective.createFloatBubble({
  researchId: "RESEARCH_ID",
});

// Create a full-viewport embed
const fullpage = Perspective.createFullpage({
  researchId: "RESEARCH_ID",
});

// Clean up without changing persisted open/closed state
widget.unmount();

// Explicitly close and persist closed state for restorable embeds
popup.destroy();
```

All creation functions return a handle with `unmount()`, `destroy()`, and `update()` methods. Use `unmount()` for framework cleanup or route changes. Use `destroy()` when you intentionally want to close the embed; popup, slider, and float embeds can persist that closed state. Float handles also expose `open()`, `close()`, `toggle()`, and `isOpen`.

The browser global also exposes `Perspective.mount(containerOrSelector, config)` for selector-based mounting, `Perspective.init(config)` for non-widget embed types, `Perspective.destroy(researchId)`, `Perspective.destroyAll()`, `Perspective.autoInit()`, `Perspective.configure(config)`, and `Perspective.getConfig()`.

### Callbacks

| Callback | Arguments | Description |
|----------|-----------|-------------|
| `onReady` | -- | Embed has loaded and is ready for interaction. |
| `onSubmit` | `data: { researchId: string }` | Participant completed the interview. |
| `onNavigate` | `url: string` | Embed requests a page navigation. If not provided, defaults to `window.location.href` (full page reload). |
| `onClose` | -- | User closed the embed (popup, slider, or float). |
| `onError` | `error: Error` | An error occurred during the embed lifecycle. |
| `onAuth` | `data: { researchId: string; token: string }` | Embed auth completed and returned a token for custom storage. |

## NPM JavaScript SDK

For TypeScript or bundler-based apps that do not need React components, install `@perspective-ai/sdk`:

```bash
npm install @perspective-ai/sdk
```

```ts
import {
  createWidget,
  openPopup,
  openSlider,
  createFloatBubble,
  createFullpage,
  configure,
  fetchEmbedConfig,
} from "@perspective-ai/sdk";

configure({ host: "https://getperspective.ai" });

const container = document.querySelector<HTMLElement>("#perspective-widget");
const widget = createWidget(container, {
  researchId: "RESEARCH_ID",
  params: { source: "app", user_id: "123" },
});

const popup = openPopup({ researchId: "RESEARCH_ID" });
const slider = openSlider({ researchId: "RESEARCH_ID" });
const float = createFloatBubble({ researchId: "RESEARCH_ID" });
const fullpage = createFullpage({ researchId: "RESEARCH_ID" });

const config = await fetchEmbedConfig("RESEARCH_ID");

widget.unmount();
popup.destroy();
float.open();
float.close();
```

`createWidget` accepts an `HTMLElement | null`. If you want selector-based mounting, use the browser global `Perspective.mount("#selector", config)` or resolve the element before calling the NPM function.

## React SDK

For React and Next.js apps, use `@perspective-ai/sdk-react` instead of the script tag. It provides typed components and hooks with full lifecycle control.

### Installation

```bash
npm install @perspective-ai/sdk-react
```

### Widget (Inline Embed)

Renders the interview directly inside a container element:

```tsx
import { Widget } from "@perspective-ai/sdk-react";

function FeedbackWidget() {
  return (
    <Widget
      researchId="RESEARCH_ID"
      params={{ source: "app", user_id: "123" }}
      onReady={() => console.log("Loaded")}
      onSubmit={() => console.log("Completed")}
    />
  );
}
```

The `Widget` component accepts all standard `div` props (`className`, `style`, etc.) for sizing and layout.

### Popup

Use the `usePopup` hook for modal overlays:

```tsx
import { usePopup } from "@perspective-ai/sdk-react";

function FeedbackButton() {
  const { open, isOpen } = usePopup({
    researchId: "RESEARCH_ID",
    onSubmit: () => console.log("Completed"),
  });

  return <button onClick={open}>Give Feedback</button>;
}
```

### Slider

Use the `useSlider` hook for a side panel that slides in from the edge:

```tsx
import { useSlider } from "@perspective-ai/sdk-react";

function SliderTrigger() {
  const { open } = useSlider({ researchId: "RESEARCH_ID" });
  return <button onClick={open}>Open Survey</button>;
}
```

### Float Bubble

A floating chat bubble anchored to the corner of the page:

```tsx
import { FloatBubble } from "@perspective-ai/sdk-react";

function App() {
  return <FloatBubble researchId="RESEARCH_ID" onSubmit={handleSubmit} />;
}
```

### Fullpage

Takes over the entire viewport:

```tsx
import { Fullpage } from "@perspective-ai/sdk-react";

function InterviewPage() {
  return <Fullpage researchId="RESEARCH_ID" />;
}
```

### Auto-Open

Trigger a popup automatically after a delay or on exit intent:

```tsx
import { useAutoOpen } from "@perspective-ai/sdk-react";

function AutoSurvey() {
  useAutoOpen({
    researchId: "RESEARCH_ID",
    trigger: { type: "timeout", delay: 5000 },
    showOnce: "session",
  });
  return null;
}
```

### React Callbacks

All components and hooks accept the same callback props as the JavaScript API: `onReady`, `onSubmit`, `onNavigate`, `onClose`, and `onError`. See the [Callbacks](#callbacks) table above for details.

React helpers also include `useFloatBubble`, `useEmbedConfig`, `useThemeSync`, and `DiscoveryMetadata` for headless float control, config fetching, theme synchronization, and server-rendered discovery metadata.

## Direct Iframe Embed

If you cannot use the SDK (e.g., in environments that restrict third-party scripts), embed the interview directly in an iframe:

```html
<iframe
  id="perspective-iframe"
  src="https://getperspective.ai/interview/RESEARCH_ID?email=user@example.com&returnUrl=https://example.com/thanks"
  style="width: 100%; height: 600px; border: none;"
  allow="microphone"
></iframe>
```

When using a direct iframe, you **must** add a `postMessage` listener to handle navigation and lifecycle events:

```javascript
var iframe = document.getElementById("perspective-iframe");
var allowedOrigin = new URL(iframe.src).origin;

window.addEventListener("message", (event) => {
  if (event.origin !== allowedOrigin) return;
  if (event.source !== iframe.contentWindow) return;
  if (!event.data?.type?.startsWith("perspective:")) return;

  switch (event.data.type) {
    case "perspective:ready":
      // Embed loaded and ready
      break;
    case "perspective:submit":
      // Interview completed
      break;
    case "perspective:redirect":
      // Navigate to the URL provided by the embed
      window.location.href = event.data.url;
      break;
    case "perspective:close":
      // User closed the embed
      break;
    case "perspective:resize":
      // Adjust iframe height: event.data.height
      iframe.style.height = event.data.height + "px";
      break;
    case "perspective:error":
      // Handle error: event.data.error
      break;
  }
});
```

### PostMessage Events

| Event | Data | Description |
|-------|------|-------------|
| `perspective:ready` | -- | Embed loaded and ready. |
| `perspective:submit` | -- | Interview completed. |
| `perspective:redirect` | `{ url }` | Embed wants to navigate. You must handle this. |
| `perspective:close` | -- | User closed the embed. |
| `perspective:resize` | `{ height }` | Embed content height changed. |
| `perspective:error` | `{ error }` | An error occurred. |

Add `allow="microphone"` to the iframe if the interview uses voice mode.

## Best Practices

- **Load the script once**: If you have multiple embeds on the same page, include the SDK script tag only once. It discovers all trigger elements automatically.
- **Use the SDK over iframes**: The SDK handles responsive sizing, navigation, theming, and lifecycle events. Direct iframes require you to manage all of this yourself.
- **Pass user identity via params**: If your users are already authenticated, pass `email` and `name` so participants skip the identification step.
- **Prefer `onNavigate` over default behavior**: In single-page apps, handle `onNavigate` to use your router instead of a full page reload.
