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

# Embedding Omni best practices

> Learn how to develop a secure, enjoyable embedded Omni experience for your users.

## Define base connection roles

When generating embed requests, set the [`connectionRoles`](/embed/setup/url-parameters/connectionRoles) parameter to either **No Access** or **Viewer**. This sets the default level of access for the connection and ensures that access to data must be granted on an individual user basis.

## Send connection roles on every embed request

When a user creates an embed session, the [`connectionRoles`](/embed/setup/url-parameters/connectionRoles) on the latest request acts as the source of truth for that user's connection access. This means:

* A role can be **downgraded** by a later request (e.g. from **Restricted Querier** to **Viewer**).
* A role can be **removed** entirely if the later request doesn't mention the connection — either explicitly in `connectionRoles` or implicitly in the connection backing the requested [`contentPath`](/embed/setup/url-parameters/contentPath).

<Warning>
  If you embed multiple pieces of content on the same page (for example, a dashboard and a workbook in separate iframes), include an explicit `connectionRoles` object on every request. Each iframe generates its own embed login request, and a request that omits `connectionRoles` will overwrite the access granted by the others.
</Warning>

For example, imagine a page that embeds:

* A workbook backed by connection A, which requires at least **Restricted Querier** access
* A dashboard backed by connection B, which only requires **Viewer** access

If the dashboard's embed request only specifies `connectionRoles` for connection B, the user's role on connection A will be deleted as soon as that request resolves — and the workbook will lose access. To avoid this, send `connectionRoles` for both connection A and connection B on **both** the workbook and dashboard embed requests.

## Leverage user attributes to control data access

To ensure appropriate data access for users, best practice is to leverage [user attributes](/administration/users/attributes) to systematically filter data.

* **Access filters**, or row-level security, allow you to restrict the rows of data a user can access within a topic. Access filters apply the values assigned on a user attribute to the `WHERE` clause of every SQL query a user runs, filtering out to only the data designated to that user.
* **Access grants** define topic- and field-level permissions for users

## Implement security for multi-tenant instances

<Warning>
  Segmenting data using hidden dashboard filters is not a secure practice.
</Warning>

Typically, companies choose one of the following strategies to set up multi-tenant customer data:

* **Row-level security** - If all of the data is inside one table, you can assign a user attribute per user and client and use it as an [access filter](/modeling/develop/data-access-control). Specifically make sure to set up a default access filter to control in Omni with [`default_topic_access_filters`](/modeling/models/default-topic-access-filters). Note that [input columns](/analyze-explore/input-columns) automatically enforce access filter fields in their mapping keys to prevent cross-tenant data leakage.
* **Schema level security** - If each client is in a separate, identical schemas then you can leverage [dynamic schemas](/modeling/models/dynamic-schemas).
* **Database level security** - If each client is in a separate database and the schemas are identical across databases, you can leverage [dynamic database environments](/connect-data/dynamic-environments).
* **Model-level isolation** - If each client needs a tailored view of the same base model, you can use [shared extension models](/modeling/develop/shared-extensions) to create tenant-specific extensions that inherit core logic while allowing per-client customizations.

## Build content on shared topics

Embedded dashboards won't render correctly if the content you want to embed meets **any** of the following criteria:

* Content contains fields not included in a topic in the shared model
* Content built on SQL
* Content that contains unpromoted changes to joins in the workbook's model

<Warning>
  To expose non-topic bound or SQL-based content, enable [**AccessBoost**](/share#boosting-permissions-with-accessboost) in the content's **Share** settings. **This has security implications**, as you may expose data that you don't want your embed users to access.
</Warning>

## Save content to the Shared hub

Along with the above criteria for building content on topics, embedded dashboards must be saved in your instance's **Shared** hub. Dashboards will not render correctly if they are saved in personal folders.

## Use unique emails per embed user

Email addresses in embed are used to resolve delivery recipients and apply [user attributes](/administration/users/attributes). Each embed user must have a unique email to ensure deliveries reach the intended recipient with the expected attributes applied.

* **Never mix internal and embed user emails.** If you must use the same base email, use the `+` modifier - for example, `blob@example.com` for the internal user and `blob+omniembed@example.com` for the embed user.
* **Never reuse the same email across different** `externalId `**values.** Each embed user should have a unique email to ensure deliveries reach the intended recipient.
* **Include the [`email`](/embed/setup/url-parameters/email) parameter when generating the embed session.** Without it, email deliveries cannot be mapped back to the embed user and Omni will create a separate email-only user instead.

## Create unique embed users when embed users access data in multiple entities

If a physical user in your application has access to data across multiple entities, provision a separate embed user for each entity rather than reusing a single embed user across all of them.

An embed user's attributes reflect their most recent embed session. Each new session overwrites the previous attributes. If the same embed user is used across entities, whichever entity was accessed most recently determines what attributes that user holds.

**Example:** A physical user who accesses both `acme` and `globex` should have two embed users:

| Physical user                               | Entity | `externalId`  | Email                     |
| ------------------------------------------- | ------ | ------------- | ------------------------- |
| [user@yourapp.com](mailto:user@yourapp.com) | acme   | `user_acme`   | `user+acme@yourapp.com`   |
| [user@yourapp.com](mailto:user@yourapp.com) | globex | `user_globex` | `user+globex@yourapp.com` |

Each embed user gets the user attributes appropriate for its entity. When the user switches between entities in your application, your server generates a new embed URL using the corresponding embed user's `externalId` and attributes.

See [user attributes](/administration/users/attributes) and [access filters](/modeling/develop/data-access-control) for details on configuring per-entity row-level security.
