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

# setEmbeddedData

> Attach context from your app to the next Formbricks response, without tying it to a trigger. The JavaScript SDK reference for setEmbeddedData and clearEmbeddedData.

In an app survey the integrator usually does not know which action will open a survey: your app fires a dozen of them and Formbricks decides. Passing context on `track()` therefore means repeating the same values on every possible call.

`setEmbeddedData()` decouples the two. You push context whenever you have it, and every survey displayed from then on picks it up — until you remove the key, or the page loads again.

```js theme={null}
formbricks.setEmbeddedData({ pageType: "product", plan: "pro" });
```

> [!NOTE]
>
> This is for **website & app surveys** only, where the JavaScript SDK runs. Link surveys have no SDK on the
> page and are filled from [URL parameters](https://formbricks.com/docs/surveys/general-features/hidden-fields.md#set-hidden-field-via-url)
> instead. For React Native, iOS, Android and Flutter see [Mobile SDKs](https://formbricks.com/docs/surveys/embedded-data/mobile-sdks.md).

## What It Writes

The bag reaches a response only through the survey's **declared ingested fields**, which are the passed-in entries in the survey editor's Embedded Data card. A key nothing declares is dropped and logged; so is a key naming a variable, a reserved field or a locked field. Nothing here is ever fatal, and a rejected key never blocks a response.

That means the workflow is always: declare the field on the survey first, then push a value under the same name.

Values are scoped to the response. They are **never** written to the contact record, so this is not a back door into contact attributes.

## Merge Semantics

`setEmbeddedData()` merges into the current bag rather than replacing it, and per key the last write wins:

```js theme={null}
formbricks.setEmbeddedData({ plan: "pro", pageType: "product" });
formbricks.setEmbeddedData({ pageType: "checkout" });
// bag is now { plan: "pro", pageType: "checkout" }
```

That is what lets you set stable fields once and refresh volatile ones as the user moves, without the second call wiping the first.

### `null` Removes, `undefined` Does Nothing

The two are deliberately different, and the difference is the whole reason the "pass every field unconditionally" idiom is safe:

```js theme={null}
formbricks.setEmbeddedData({ pageType: null });      // removes pageType
formbricks.setEmbeddedData({ pageType: undefined }); // no-op — pageType keeps its value
```

A tag manager or a shared helper typically reads keys off a data layer and passes all of them on every page. On a page where `pageType` does not exist the key arrives as `undefined`, and it must not clear the value the previous page set. So `undefined` is a documented no-op, not an accident.

### Accepted Values

`string`, `number`, `boolean` and `Date`, plus `null` to remove a key and `undefined` to skip it. Dates are serialized as ISO 8601, which is what a `date` field accepts.

Anything else is refused rather than mangled. Passing a primitive, an array or a missing object logs an error and writes nothing, so `setEmbeddedData(dataLayerObj)` on a page where that object does not exist is a survivable mistake rather than a broken tag. Arrays in particular would otherwise spread into junk numeric keys.

## Clearing

```js theme={null}
formbricks.clearEmbeddedData("pageType"); // remove one key
formbricks.clearEmbeddedData();           // clear the whole bag
```

Only a literal zero-argument call clears everything. `clearEmbeddedData(someValue)` where `someValue` turned out to be `undefined` is a logged no-op, for the same reason as above: code that reads the key name from its own state must not wipe the bag when that state is empty.

## Lifetime

**1. In memory, never persisted**

The bag lives in SDK memory for the page load. It is not written to `localStorage`, so a full page load
starts empty and your app re-pushes. A classic multi-page app does that for free on every navigation.

**2. Snapshot at display, then frozen**

When a survey is shown, the bag is copied onto it. A `setEmbeddedData()` call made while that survey is
on screen reaches the **next** response, never the one being answered.

**3. Cleared on an identity switch**

Calling `formbricks.logout()`, or `formbricks.setUserId()` with a **different** id, clears the bag: one
user's context must not ride onto the next user's responses on a shared device. Identifying for the first
time keeps the bag, because pushing context before you know who the user is is a legitimate order.

## Stable vs. Volatile Fields

Two patterns, and mixing them up is the one failure mode worth designing against.

**Stable** context (`plan`, `role`, `accountType`) changes rarely. Set it once after setup and let the next page load re-push it.

**Volatile** context (`pageType`, `screen`, `channel`) is only true for the page you are on. In a single-page app one page load spans many routes, so nothing re-pushes on your behalf and a value set on `/pricing` is still in the bag when a survey opens on `/settings`.

```js theme={null}
// Call this from your router's navigation hook
function onRouteChange(route) {
  // `null` clears the key when the new route has no page type,
  // so the previous route's value cannot leak onto the next response
  formbricks.setEmbeddedData({ pageType: route.pageType ?? null });
  formbricks.registerRouteChange();
}
```

Setting the key to `null` when the new route has no page type is what stops the previous route's value from leaking. `setEmbeddedData()` is a synchronous memory write with no network call, so doing this on every route change costs nothing.

## Calling It Before Setup

`setEmbeddedData()` is synchronous and does not go through the SDK's command queue, so a call made before `setup()` finishes is kept rather than dropped. Two things do have to be true first, and they have different answers.

**The script must have loaded.** `window.formbricks` does not exist until then, and neither does anything on it — `setEmbeddedData` and `on` alike. Push your context from the script tag's own `onload`, or from a Google Tag Manager tag: the dataLayer buffers across load order, so a GTM trigger cannot be too early. Do not poll for the global.

**`setup()` may be delayed, and that is what the readiness event is for.** When the script has loaded but `setup()` waits on something like a consent banner, subscribe first and push when it resolves:

```js theme={null}
// the script has loaded; setup() has not been called yet
formbricks.on("formbricks_setup_successful", () => {
  formbricks.setEmbeddedData({ plan: currentUser.plan });
});
```

The order matters. Events are delivered live rather than replayed, so a handler registered after `setup()` has already finished never fires and logs nothing. See [Lifecycle Events](https://formbricks.com/docs/surveys/website-app-surveys/lifecycle-events.md) for the full list and the subscription API, and [Google Tag Manager](https://formbricks.com/docs/surveys/website-app-surveys/google-tag-manager.md#set-embedded-data) for the tag-manager wiring.

## Checking What Is in the Bag

The bag is invisible: it is memory, so there is nothing in devtools storage, and the API has no getter. Add `?formbricksDebug=true` to your URL and every write prints:

```text theme={null}
setEmbeddedData: set [pageType], removed [plan] — the bag now holds [pageType, accountType].
Keys land on a response only if the survey declares them as ingested Embedded Data fields.
```

`clearEmbeddedData()` traces too, including the "that key was not in the bag" case, which is usually the answer to "why is my value not there".

> [!NOTE]
>
> The trace prints **keys only, never values**, and only at debug level, so a respondent's console stays
> clean and nothing you pushed is echoed into it.

## Precedence Against `track()`

The older `formbricks.track("action", { hiddenFields: { … } })` form still works and still wins. An explicit per-trigger value beats the ambient bag for the same field, whatever the casing on either side, so migrating one call site at a time is safe.

> [!NOTE]
>
> Two ways to load the SDK, two ways to pick this up. The script served by your Formbricks instance
> (`/js/formbricks.umd.cjs`) carries it as soon as the instance is updated, because that bundle is served
> rather than installed. The npm package proxies the SDK's methods explicitly, so there you get it by bumping
> `@formbricks/js`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.