Skip to main content

Embedding a Private ToolJet Application

ToolJet allows you to securely embed your private applications inside portals, dashboards, or third-party systems without requiring your users to log in again.

Embedded private apps continue to respect all permissions and workspace access controls, ensuring that users only see the data and features they are authorized for. Each embedded session is scoped and isolated, so access is controlled per user per application without interfering with other ToolJet sessions.

Key Benefits:

  • Secure by default: Private apps remain inaccessible without authenticated sessions
  • No login friction: Embedded apps load seamlessly inside iframes without redirects
  • Fine-grained access control: Sessions inherit existing app and workspace permissions
  • Session isolation: Embedded usage does not interfere with primary ToolJet sessions
  • Backend-controlled access: Authentication and session lifecycle are fully managed server-side

When to Use Private App Embedding

  • Embed sesitive ToolJet apps inside customer portals or internal dashboards
  • Integrate ToolJet workflows into existing SaaS products or admin systems
  • Expose sensitive operational tools without making them public
  • Reuse ToolJet apps as a secure internal tool layer within your platform

Authentication Flow

  1. User logs into your portal using your preferred authentication method.
  2. Your backend generates a personal access token (PAT) scoped to that user and app.
  3. Backend returns an embed URL containing the PAT.
  4. Frontend renders the ToolJet app inside an iframe using the embed URL.
  5. The embedded session is scoped and isolated, ensuring that the user has access only to the intended app without affecting other ToolJet sessions.

See the diagram below for a detailed view of the authentication and embedding flow.

Auth Flow for Embedding Private ToolJet Application

Steps to Embed a Private Application

  1. Create and release your ToolJet application.

  2. Configure Your Frontend: Your frontend needs to request an embedded app URL from your backend immediately after the user logs in. The same email used for login will be used to generate the ToolJet embed URL.

    <!DOCTYPE html>
    <html>
    <body>
    <div id="app-container">
    <p>Loading your dashboard...</p>
    </div>

    <script>
    // This function runs after user successfully logs in
    async function onUserLogin(userEmail) {
    // App ID of the ToolJet app you want to embed
    const appId = '8ba8bf0e-6b8f-4e07-abb9-6fd2d816fabc';

    try {
    // Request the embed URL from your backend
    const response = await fetch('/api/get-tooljet-url', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify({
    email: userEmail,
    appId: appId
    })
    });

    const data = await response.json();

    if (data.redirectUrl) {
    // Create and insert iframe
    const iframe = document.createElement('iframe');
    iframe.src = data.redirectUrl;
    iframe.width = '100%';
    iframe.height = '600px';
    iframe.style.border = 'none';

    document.getElementById('app-container').innerHTML = '';
    document.getElementById('app-container').appendChild(iframe);
    }
    } catch (error) {
    console.error('Error loading ToolJet app:', error);
    document.getElementById('app-container').innerHTML =
    '<p>Failed to load dashboard. Please refresh the page.</p>';
    }
    }
    // Call this after your authentication succeeds
    // Example: after successful login response
    // onUserLogin('[email protected]');
    </script>
    </body>
    </html>
  3. Configure Your Backend: Your backend is responsible for securely calling the ToolJet API to generate a Personal Access Token and return the embed URL.

    const express = require('express');
    const axios = require('axios');
    const app = express();

    app.use(express.json());

    // Your ToolJet configuration
    const TOOLJET_HOST = 'https://your-tooljet-instance.com'; // or http://localhost:3000
    const TOOLJET_API_TOKEN = 'your_basic_auth_token_here'; // Keep this secret!

    app.post('/api/get-tooljet-url', async (req, res) => {
    const { email, appId } = req.body;

    // Validate the request
    if (!email || !appId) {
    return res.status(400).json({ error: 'Email and appId are required' });
    }

    try {
    // Call ToolJet API to generate PAT
    const response = await axios.post(
    `${TOOLJET_HOST}/api/ext/users/personal-access-token`,
    {
    email: email,
    appId: appId,
    sessionExpiry: 60, // Session valid for 60 minutes
    patExpiry: 3600 // Token valid for 1 hour (3600 seconds)
    },
    {
    headers: {
    'Authorization': `Basic ${TOOLJET_API_TOKEN}`,
    'Content-Type': 'application/json'
    }
    }
    );

    // Return the redirect URL to frontend
    res.json({
    redirectUrl: response.data.redirectUrl
    });

    } catch (error) {
    console.error('ToolJet API Error:', error.response?.data || error.message);

    // Handle specific error cases
    if (error.response?.status === 404) {
    return res.status(404).json({ error: 'User not found in ToolJet' });
    }
    if (error.response?.status === 403) {
    return res.status(403).json({ error: 'User does not have access to this app' });
    }
    if (error.response?.status === 429) {
    return res.status(429).json({ error: 'Too many requests. Please try again later.' });
    }

    res.status(500).json({ error: 'Failed to generate embed URL' });
    }
    });

    app.listen(3001, () => {
    console.log('Server running on port 3001');
    });
  4. Dynamically Update the iframe: The iframe is automatically created and loaded once the redirectUrl is received from your backend. Use conditional rendering to display the iframe only when the URL is available.

    function displayToolJetApp(redirectUrl) {
    const container = document.getElementById('app-container');

    if (redirectUrl) {
    const iframe = document.createElement('iframe');
    iframe.src = redirectUrl;
    iframe.width = '100%';
    iframe.height = '600px';
    iframe.style.border = 'none';
    iframe.title = 'ToolJet Application';

    // Clear loading message and add iframe
    container.innerHTML = '';
    container.appendChild(iframe);
    }
    }