Skip to main content

Embed a published app

By default, Retool blocks other pages from framing your apps as one of many mechanisms that keep your apps safe. To place a published app in an iframe, an admin adds Content Security Policy directive rules that name the pages allowed to frame it. This guide covers the three places you can embed an app: an external website, another app, and a classic app.

note

Retool does not currently support public links for apps, which make your app available to anyone without authentication.

Requirements​

You must be an admin or have the delegated Manage advanced settings permission to change these rules. Refer to Customize the Content Security Policy for apps for how the policy works and which source expressions are accepted.

Each app you embed must be published. Refer to Publish an app for the URL of a published app.

Required directives​

To embed your apps while keeping them secure, set one or both of the following directives in the Content Security Policy settings.

DirectiveApplies toDefaultEffectRequired for
frame-ancestorsAll apps in your organization.'none'Names the pages allowed to frame the app.All parent types (apps, classic apps, and external websites).
frame-srcThe page holding the iframe.'self'Names what that page can load in an iframe.When embedding an app in another app.
important

Rules take up to 30 minutes to apply across all of your apps.

Embed an app in an iframe​

Set frame-ancestors to allow a parent website or app to frame your app:

  1. Go to Settings > App security > Content Security Policy.
  2. Select Add rule, and choose the frame-ancestors directive. Enter the origin of the page that holds the iframe (which is the scheme, host, and optional port), such as https://portal.example.com. To allow several subdomains of one site, use a single-level wildcard such as https://*.example.com.
  3. Add an iframe to that page with the published app URL as its src.
Parent website
<iframe
src="https://retool.example.com/rr/app/support-tool"
width="100%"
height="800"
></iframe>

Pass data between embedded and parent apps​

To pass data between the parent app and the embedded app, use the browser's postMessage API.

When a customer support rep clicks Resolve, send the ticket ID and status to the parent window using postMessage.
Once the PTO request is submitted, send the employee ID to the parent app.

To align your parent app and embedded app's postMessage events for interactivity, Retool suggests using Retool's CLI or MCP server with external source control to build the communication path for both apps at once.

Configure custom authentication​

If the parent app is not a Retool app, you can use custom authentication with the Retool API to automatically authenticate the user.

note

This flow is available for apps published on a cloud instance that use the standard Retool domain (example.retool.com). It is not available to self-hosted instances or organizations that use a custom domain, because their apps are published on a different URL.

When parent app authenticates a user using your preferred authentication method, your backend must make a request to Retool to generate an embed URL. This embed URL is a secure, single-use link on the domain of your published app. Loading it in the iframe exchanges the link for a session, sets the cookies the app needs, and then displays the app. The following diagram illustrates the authentication flow.

Loading diagram...

The app must already be published, and the origin of the page that holds the iframe must have a frame-ancestors rule, as described in Embed an app in an iframe. Custom authentication does not replace that rule.

Generate an access token​

First, an admin must create an access token with the Retool Embed scope, listed under Apps. This token allows you to create sessions for embedding apps.

  1. Go to Settings > Retool API and click Create new to generate an access token.
  2. Enter a name and description, then select the required scopes.
  3. Copy and save your token, as you can only access it once.

Create permission groups for your users​

An admin must create a permission group that determines the apps users can access.

To create a permission group:

  1. Navigate to Settings > Groups.
  2. Click Create new in the top right and complete the form to create a group.
  3. On the group's Apps tab, enable Use access to the app you want users to have access to. Refer to Share apps for more information.

Make sure to note the Group ID for the group you want to give access to. You can find a group's ID by hovering over the group, clicking the menu, and then selecting Copy group ID. Retool adds the external user to the groups you specify when it creates the session.

Create an embed URL​

The embed URL is a single-use link to an app for an authenticated user. Create this URL by sending an API request from the backend of your parent app to to POST /api/embed-url/external-user with the required parameters. Retool returns the URL, which you can use in the frontend of your parent app to display the embedded app.

Refer to Create an Embed URL for the full API specification.

The following examples send the request to Retool and return the embed URL to your frontend. Replace APP_UUID, GROUP_IDS, and USER_ID with your own values.

curl -X POST "https://exampleorg.retool.com/api/embed-url/external-user" \
-H "Authorization: Bearer retool_01hn417x8zsaqfye58r995re18" \
-H "Content-Type: application/json" \
-d '{ "landingPageUuid": "APP_UUID", "groupIds": GROUP_IDS, "externalIdentifier": "USER_ID" }'

Retool responds with a URL on the domain of your published app.

Response
{
"embedUrl": "https://exampleorg--support-tool.retool.app/embed-redirect?nonce=NONCE&destination=%2F"
}

Use the embed URL to display the app​

On your frontend, request the embed URL from your backend and set it as the src of an iframe.

const RetoolWrapper = ({ retoolAppName, userJwt }) => {
const [retoolEmbedUrl, setRetoolEmbedUrl] = useState("");

useEffect(() => {
// Each embed URL works once, so request a new one whenever the app or the
// signed-in user changes, and ignore a response that arrives after a switch.
let current = true;
setRetoolEmbedUrl("");

const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userJwt, retoolAppName }),
};
fetch("/api/embedUrl", options)
.then((res) => res.json())
.then((data) => {
if (current) {
setRetoolEmbedUrl(data.embedUrl);
}
});

return () => {
current = false;
};
}, [retoolAppName, userJwt]);

return (
retoolEmbedUrl && (
<iframe
src={retoolEmbedUrl}
style={{ width: "100%", height: "800px", border: "none" }}
/>
)
);
};

Each embed URL works only once. Request a new URL from your backend each time the page that holds the iframe loads.

Control data access with metadata​

You can pass information in the metadata object to dynamically control the data users see and the behavior within an app. Retool stores these values on the user and returns them in the Current User object as current_user.metadata when the app loads.

The values come from the request your backend makes, not from the page that holds the iframe, so a user cannot change them in the browser.

Retool merges the values you send with any metadata the user already has. A key you stop sending remains set from an earlier request, so send an explicit value for every key the app reads.

Testing​

When generating the embed URL, Retool may create an additional user depending on the data that is passed into your POST request.

To avoid creating an extra user while testing, pass in the email associated with your Retool account in the email property of the userInfo object, as well as the IDs of the permission groups you're in.

If an email is specified and it matches that of an existing Retool user, Retool updates the user, its metadata, and permission groups. It creates a new user if the email does not exist. If no email is specified, Retool updates the user if the externalIdentifier matches, or creates a new user if the externalIdentifier does not exist. If the externalIdentifier and the email belong to two different users that already exist in your organization, the request fails.

Troubleshooting​

Use this section to diagnose and resolve common issues with apps in an iframe.

Why is the iframe blank?​

Open your browser's developer console, and find the Content Security Policy violation. The violation names the directive that blocked it.

  • A frame-ancestors violation means the parent page origin is missing from that directive. The following error appears in your developer console: Content Security Policy of your site blocks some resources.
  • A frame-src violation means the parent page is a Retool app and the origin of the app in the iframe is missing from that directive. The following error appears in your developer console: Ensure CORS response header values are valid.

Add the missing origin, then reload the parent page.

Why does a request for an embed URL fail?​

Retool rejects the request to /api/embed-url/external-user in the following cases. The response body names the reason.

Failure reasonResolution
The app has no published version.Publish the app, then request the URL again.
The request includes environment, branch, or releaseVersion.Remove these parameters since they apply to classic apps only.
The API token is missing the Retool Embed scope, which returns a 401 response.Create a token with that scope.
The externalIdentifier and the email in userInfo belong to two different users that already exist in your organization.Pass values that refer to the same user.

Why does the app ask the user to reload the page?​

The embed URL could not be exchanged for a session, because each URL works only once and the URL was loaded a second time or has expired. A reload of the parent page requests a new URL, which succeeds. If the message persists, confirm your backend requests a new embed URL for each load rather than reusing one.

Why does the camera or microphone not work?​

A browser grants an iframe only the features the page holding it delegates, so an app that works on its own can lose these features once embedded. Video and audio capture, fullscreen, and DRM playback are the usual ones.

  • In an external website or another app, list the features in the allow attribute of the iframe element, such as <iframe src="EMBED_URL" allow="camera; microphone; fullscreen"></iframe>.
  • In a classic app, enable the matching options on the IFrame component, such as Camera, Microphone, Fullscreen, and Encrypted media.

If the app instead loads media or an embedded player from another site, allow that origin in your organization's Content Security Policy. Refer to Customize the Content Security Policy for apps.

Why has a rule not taken effect?​

Rules take up to 30 minutes to apply across all of your apps. Reload the parent page after that time. Confirm the rule records an origin with a scheme, such as https://portal.example.com, and not a bare host name.