# Embed or eject

"Put my dashboard inside my own app" has two answers here, and they are genuinely different products rather than two settings of one.

* **Embed** — we render the dashboard; you put it in an `<iframe>`. You write no dashboard code and own no build. [Embedding a dashboard](/api/embedding-the-share-iframe.md).
* **Eject** — we hand you the React and SQL source; you render it. We leave the render path entirely and keep only the data engine underneath. [Eject your dashboard](/guides/eject-your-dashboard.md).

Both keep the platform half — connections, datasets, refreshes, schedules, the query engine, governance — maintained on our side. The question is only who draws the pixels.

## The short answer[​](#the-short-answer "Direct link to The short answer")

**Embed** if you need per-viewer row filtering, or if you want publishing to be the whole deploy. **Eject** if you need the dashboard to look and behave like the rest of your app, or if you need it to run somewhere we do not reach.

## Side by side[​](#side-by-side "Direct link to Side by side")

|                                      | Embed                                                                           | Eject                                                                            |
| ------------------------------------ | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Who renders                          | we do                                                                           | you do                                                                           |
| What you integrate                   | a URL (plus a short-lived token on the protected lane)                          | a source tree you build and host                                                 |
| Who deploys a change                 | nobody — republish and the frame serves it                                      | you, on your own build and release                                               |
| Per-viewer row filtering             | **yes**, server-enforced from a signed token — this is the only place it exists | not on the credential-free lane; needs your own server in the path               |
| Visual and interaction control       | our renderer, our chrome                                                        | complete — it is your components                                                 |
| Filter state shared with your own UI | no, in neither direction                                                        | yes, it is your state                                                            |
| Routing, auth, layout integration    | iframe boundary                                                                 | native to your app                                                               |
| Credential you must hold             | none on the public lane; an API key on your backend for the protected lane      | none on the credential-free lane; an API key on your server for the backend lane |
| Needs a backend of your own          | only for the protected lane, to mint tokens                                     | only if you want row scoping or live queries                                     |
| Data freshness                       | live queries per load                                                           | live on the backend lane; the published snapshot on the credential-free lane     |

## Pick embed when[​](#pick-embed-when "Direct link to Pick embed when")

* **Different viewers must see different rows.** This is the deciding one. Row-level filtering lives in the signed-token embed lane and nowhere else: your backend mints a short-lived token carrying the filters, the platform rewrites each dataset reference into a filtered relation, and the page can only narrow that further. Nothing you build on an ejected tree reproduces it without putting your own server in front of every query.
* **You want publishing to be the deploy.** A republish changes what every embed serves. No release on your side, no version of the dashboard pinned inside your bundle.
* **You do not want to own dashboard code.** No build, no dependency upgrades, no chart-library churn.
* **The dashboard is a panel, not the product.** A reporting tab, a customer-facing statement, an admin view.

## Pick eject when[​](#pick-eject-when "Direct link to Pick eject when")

* **It has to look like your app.** Your design system, your typography, your empty states, your loading skeletons. An iframe is a rectangle with someone else's chrome in it.
* **It has to interact with your app.** Your filters driving its charts, its selections driving your page. The embed lane does not pass filter state in either direction, so cross-filtering between a dashboard and your own UI is not available there.
* **You need your own routing, auth or layout.** Deep links into a tile, your session, your shell, your breadcrumbs.
* **It has to run where we do not.** Your own hosting, your own VPC, a CDN you control. The source is plain React and SQL and it builds with `npm install && npm run dev`. Note that ejecting moves the *rendering* to your infrastructure, not the data: an ejected dashboard still reaches this account's query engine or its Parquet extract over the network. If the requirement is that no data leaves your environment at all, that is [Vibedasher Server](/index.md#vibedasher-cloud-or-vibedasher-server), not eject.
* **You want no credential and no backend at all.** A public, published dashboard ejects onto a lane that downloads a Parquet extract and runs every query in the visitor's browser — no key, no token, nothing of yours to run or pay for. Read what that trades away in [the wasm query lane](/guides/the-wasm-query-lane.md).

## Three differences that are hard to reverse[​](#three-differences-that-are-hard-to-reverse "Direct link to Three differences that are hard to reverse")

Everything above you can change your mind about cheaply. These three you should decide before you build.

### Row scoping is an architecture choice, not a flag[​](#row-scoping-is-an-architecture-choice-not-a-flag "Direct link to Row scoping is an architecture choice, not a flag")

If you ship an ejected dashboard on the credential-free lane and later need viewer A to see fewer rows than viewer B, there is no setting to turn on. The extract in the browser is the whole dataset, so every visitor already has every row. Getting scoping back means putting a server of yours between the viewer and the data and rewriting the dashboard's query lane against it — or moving to the embed lane, which is a different integration. See [who can see what](/guides/who-can-see-what.md#row-level-filtering-the-embed-lane-and-only-the-embed-lane).

### Once you edit an ejected tree, the app is no longer the source of truth[​](#once-you-edit-an-ejected-tree-the-app-is-no-longer-the-source-of-truth "Direct link to Once you edit an ejected tree, the app is no longer the source of truth")

Eject is a real fork, which is the point of it. You can push an edited tree back to the draft branch and keep working in the loop, but if you take the source into your own repository and start changing it there, later changes made in the app do not flow into your copy and yours do not flow back. Decide which side owns the code.

### Visibility choices on the embed lane are not symmetric[​](#visibility-choices-on-the-embed-lane-are-not-symmetric "Direct link to Visibility choices on the embed lane are not symmetric")

The protected lane requires the dashboard's visibility to be `protected`, and refuses a `public` one outright — so that you cannot believe you have row filters when you do not. A `private` dashboard is not embeddable by either lane. And making a dashboard world-readable is the one irreversible step in the publish flow; see [publish and share](/guides/publish-and-share.md).

## You can do both[​](#you-can-do-both "Direct link to You can do both")

They are not exclusive, and a common shape uses each for what it is good at: embed the customer-facing view where row filtering is the whole requirement, and eject the internal view that has to sit inside your own admin shell. Same datasets, same refreshes, same governance underneath.

## Next[​](#next "Direct link to Next")

* **[Embedding a dashboard](/api/embedding-the-share-iframe.md)** — both embed lanes, the token-refresh loop, the messages your page actually receives, and where the frame's own boundary sits.
* **[Eject your dashboard](/guides/eject-your-dashboard.md)** — the three calls, the paging contract, and how to wire the data back in.
