Documentation Index

Fetch the complete documentation index at: https://docs.unqork.io/llms.txt

Use this file to discover all available pages before exploring further.

Embedded UI In-Depth Setup Guide

Prev Next

This guide covers every configuration option available when embedding Unqork modules and workflows in an external web page. It walks through environment preparation, authentication strategies, rendering modes, workflow mounting, error handling, lifecycle management, and state persistence. By the end, a fully configured embedded integration handles authentication, renders in the correct mode, catches errors, and recovers gracefully.

Understanding This Document

Values and setting selections use specific text formats. Refer to the legend below for formatting conventions.

Value legend: inline code — typed value  |  Bold — selected from a drop-down, radio button, tab, or choice chip  |  ON/OFF — toggle state

Prerequisites

Complete these steps before beginning:

  • An Unqork environment with the Embedded UI feature enabled. The embedded.js script returns a 404 error if the feature is not active.
  • The host page domain (including port) added as a Cross-Origin Resource Sharing (CORS) exception in Environment Administration > Cross-Origin Resource Sharing (CORS). Browsers block cross-origin API requests by default. Without this exception, every runtime API call fails.
  • At least one published module with a known module ID (a 24-character MongoDB ObjectId). The runtime fetches the module definition by ID before rendering. Unpublished modules are not accessible.
  • For workflow embedding, a published workflow application with a known workflow path. The path is the segment from the application URL that identifies the workflow.
  • Basic familiarity with HTML, JavaScript, and browser developer tools. The integration requires writing initialization scripts and reading network responses to diagnose issues.

Step 1: Prepare the Host Page

Every embedded integration begins with a minimal HTML page. The host page owns the layout; Unqork renders inside a container you define.

  1. Create an HTML file with a standard document structure:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Application</title>
</head>
<body>
  <h1>My Application</h1>

  <!-- Mount target — the module renders here -->
  <unqork-app></unqork-app>

  <!-- Embedded script — always at the end of body or with defer -->
  <script src="https://your-environment.unqork.io/embedded.js" defer></script>

  <!-- Initialization script -->
  <script>
    // Steps 3 and beyond go here
  </script>
</body>
</html>

Note: The <script> tag for embedded.js must load after the DOM is ready. Place it at the end of <body> or use the defer attribute. If it loads before the DOM, the mount target element will not exist and mounting will fail.

  1. The mount target element can be any hyphenated custom element tag name. Common choices include:

    Tag Name Notes
    unqork-app Default convention.
    my-module Any hyphenated name works (custom elements require a hyphen).
    div#my-container A plain div with an ID works when using iframe mode.

Important: Custom element tag names must contain a hyphen (for example, unqork-app, not unqorkapp). The hyphen is a Web Components specification requirement.

Step 2: Understand the Runtime Object

When embedded.js loads, it creates a global window.unqork object with the following structure:

window.unqork = {
  runtimes: {
    default: UnqorkRuntime  // The primary runtime instance
  }
}

All API calls use window.unqork.runtimes.default. A shorthand variable makes code cleaner:

const runtime = window.unqork.runtimes.default

Step 3: Initialize the Runtime

Before mounting anything, the runtime must be initialized. The initialize() method accepts an optional configuration object.

await runtime.initialize({ autoLogin: false })
Parameter Type Default Description
autoLogin boolean false When set to true, the runtime silently attempts anonymous authentication before mounting. If anonymous access is not available for the target module, a login pop-up opens automatically. When set to false, you must handle authentication manually before calling mountModule().

When to Use autoLogin

  • autoLogin: true: Best for public-facing pages where the module supports anonymous access, or where you want the runtime to handle the full auth flow without custom code.
  • autoLogin: false (default): Best when you need to control authentication yourself. For example, use this option when authenticating through SAML, OIDC, or a refer string before mounting.

Step 4: Handle Authentication

Authentication determines whether an end-user can access the module. Embedded UI supports multiple authentication strategies. Choose the one that matches your environment.

Option A: Anonymous Authentication

Use anonymous authentication when the module has Enable Anonymous Access turned ON in its settings.

const runtime = window.unqork.runtimes.default
await runtime.initialize()

await runtime.authenticateAnonymous({ moduleId: '696f9c76f9325e1f580152c7' })
await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})
Parameter Type Required Description
moduleId string Yes The module to authenticate against. Anonymous access is granted per-module.

Option B: Auto-Login

Let the runtime handle authentication automatically. It tries anonymous access first, then falls back to a login pop-up.

const runtime = window.unqork.runtimes.default
await runtime.initialize({ autoLogin: true })

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})

No additional code is needed. The runtime handles the entire flow.

Option C: SAML Authentication

Redirect through a Security Assertion Markup Language (SAML) identity provider using an iframe.

const runtime = window.unqork.runtimes.default
await runtime.initialize()

const authFrame = document.createElement('iframe')
authFrame.src = runtime.getSamlEntrypointUrl('my-idp-name')
authFrame.style.display = 'none'
document.body.appendChild(authFrame)

await runtime.waitForAuthFrameCompletion(authFrame)
authFrame.remove()

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})
Method Parameter Description
getSamlEntrypointUrl(idp) idp: the SAML identity provider name configured in Unqork Returns the full SAML redirect URL.
waitForAuthFrameCompletion(iframe) iframe: the iframe DOM element Returns a promise that resolves when authentication completes.

Option D: OIDC Authentication

Redirect through an OpenID Connect (OIDC) provider using an iframe.

const runtime = window.unqork.runtimes.default
await runtime.initialize()

const authFrame = document.createElement('iframe')
authFrame.src = runtime.getOidcEntrypointUrl('my-oidc-provider')
authFrame.style.display = 'none'
document.body.appendChild(authFrame)

await runtime.waitForAuthFrameCompletion(authFrame)
authFrame.remove()

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})

Option E: Refer String Authentication

Use a server-issued refer token for SSO-like flows.

const runtime = window.unqork.runtimes.default
await runtime.initialize()

await runtime.authenticateReferString('eyJhbGciOiJIUzI1NiIs...')
await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})

Option F: Login Form Pop-Up

Open the standard Unqork login form in a pop-up window.

const runtime = window.unqork.runtimes.default
await runtime.initialize()

const loginWindow = window.open(
  runtime.getLoginEntrypointUrl(),
  'unqork-login',
  'width=500,height=600'
)
await runtime.waitForAuthFrameCompletion(loginWindow)
loginWindow.close()

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})

Checking Authentication Status

At any point, check whether a valid session exists.

const isAuth = await runtime.isAuthenticated()

Returns true if the end-user has a valid session cookie, false otherwise.

Step 5: Mount a Module

Choose the rendering mode that fits your use case. Web component mode is the default and works for all modules. Iframe mode provides stronger isolation but is only available for Runtime 1.0 modules and returns a different API object.

Option A: Web Component Mode (Default)

The default rendering mode uses a Shadow DOM web component. The module renders inside an isolated shadow root, preventing CSS leakage between the host page and the module.

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app'
})

mountModule Options

The following parameters are available when calling mountModule().

Parameter Type Required Default Description
moduleId string Yes The 24-character MongoDB ObjectId of the module to mount.
target string Yes The custom element tag name (for example, unqork-app) or CSS selector (for example, #my-container) for the mount point.
style string No Optional style name override. Maps to a named style set in Style Administration.
moduleData object No Pre-fetched module definition JSON. When provided, skips the internal API call to /fbu/form/{moduleId}, avoiding a duplicate request. Useful when you have already fetched the module definition for other purposes.
iframe boolean No false When set to true, mounts the module inside a sandboxed <iframe> instead of a Shadow DOM web component. Only available for Runtime 1.0 modules. See Option B for details.
sandbox string No allow-scripts allow-same-origin allow-forms Custom sandbox attribute for the iframe. Only applies when iframe is set to true.

Runtime Auto-Detection

The runtime version is determined automatically from the module definition.

  • settings.runtimeVersion starting with 1. or missing → Runtime 1.0
  • settings.runtimeVersion starting with 2. → Runtime 2.0

No configuration is needed. The correct runtime loads automatically.

Pre-Fetching Module Data

To avoid a duplicate API call (one for runtime detection, one for module loading), pass the module definition directly:

const response = await fetch('https://your-env.unqork.io/fbu/form/696f9c76f9325e1f580152c7', {
  credentials: 'include'
})
const moduleData = await response.json()

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app',
  moduleData
})

Option B: Iframe Mode

Iframe mode provides stronger isolation at the cost of reduced API access. The module runs in a separate browsing context with its own window and document.

const iframeApi = await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app',
  iframe: true
})

Note: Iframe mode is only available for Runtime 1.0 modules. Attempting to use iframe mode with a Runtime 2.0 module returns an error.

IframeAPI Return Value

When iframe is set to true, mountModule returns an IframeAPI object instead of an HTMLElement.

Data Access Methods

Use these methods to read and write component data inside the iframe. Because the module runs in a separate browsing context, direct DOM access from the host page is not reliable. These methods provide a controlled API that works across the iframe boundary.

// Get all submission data
const data = await iframeApi.getSubmissionData()

// Get a single component value
const value = await iframeApi.getComponentValue('textfield1')

// Set a component value
await iframeApi.setComponentValue('textfield1', 'Hello World')

// Get a component definition
const def = await iframeApi.getComponentDefinition('textfield1')
Actions

Use these methods to trigger actions inside the iframe. The host page cannot directly call functions on the module, so these methods send messages across the iframe boundary on your behalf.

// Trigger form submission
await iframeApi.triggerSubmit()
Events

Subscribe to events dispatched by the module inside the iframe. Because the module runs in its own browsing context, its internal events do not reach the host page. These listeners receive forwarded events from the runtime so the host page can respond to submissions, errors, and navigation.

// Listen for submission
const unsubscribe = iframeApi.on('submit', (payload) => {
  console.log('Submitted:', payload.submissionId, payload.data)
})

// Listen for errors
iframeApi.on('error', (payload) => {
  console.error('Validation errors:', payload.errors)
})

// Listen for navigation
iframeApi.on('navigate', (payload) => {
  console.log('Navigated to:', payload.formId)
})

// Unsubscribe
unsubscribe()
Event Payload When
submit { submissionId, data } Form submitted successfully.
error { errors, message } Validation or submission error.
navigate { formId, state } Module navigation occurred.
locationChange { hash } URL hash changed inside iframe.

Custom Sandbox Attributes

Override the default sandbox policy to control what the iframe can access.

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app',
  iframe: true,
  sandbox: 'allow-scripts allow-same-origin allow-forms allow-popups'
})
Sandbox Value Purpose
allow-scripts Required: the module uses JavaScript.
allow-same-origin Required: the module needs cookie access for authentication.
allow-forms Required: the module contains form elements.
allow-popups Optional: add if the module opens pop-ups (for example, file uploads).

Step 6: Mount a Workflow

Workflows render a multi-step sequence of modules with navigation controls like Next, Previous, and Save and Exit.

await runtime.mountWorkflow({
  workflowPath: 'insurance-onboarding',
  target: 'unqork-app'
})

mountWorkflow Options

The following parameters are available when calling mountWorkflow().

Parameter Type Required Default Description
workflowPath string Yes The workflow path identifier. The path segment from the application URL (for example, insurance-onboarding from /app/insurance-onboarding).
target string Yes The custom element tag name or CSS selector for the mount point.
submissionId string No A submission ID for resuming a previously saved workflow. When provided, the workflow loads from the saved step and data.
style string No Optional style name override.
persistWorkflowState boolean No false When set to true, automatically saves the current submissionId and stepPath to localStorage on each step transition. On page reload, the workflow resumes from the saved state. When set to false, navigation events are still emitted but no automatic persistence occurs.

Determining the workflowPath

The workflowPath is the path segment from the Unqork application preview URL:

http://env.unqork.io/app/insurance-onboarding?preview=true#/workflow/insurance-onboarding/step-1
                          ^^^^^^^^^^^^^^^^^^^^
                          This is the workflowPath

Resuming a Workflow

To resume from a previous save point, pass the submissionId.

await runtime.mountWorkflow({
  workflowPath: 'insurance-onboarding',
  target: 'unqork-app',
  submissionId: '69e13f32f8681734737a1a27'
})

Automatic State Persistence

When persistWorkflowState is set to true, the workflow state is saved to localStorage after each navigation. If the end-user reloads the page, the workflow automatically resumes.

await runtime.mountWorkflow({
  workflowPath: 'insurance-onboarding',
  target: 'unqork-app',
  persistWorkflowState: true
})

The saved state expires after 24 hours.

Manual State Persistence through Events

When persistWorkflowState is set to false, the default, listen for the navigation event and handle persistence in your own code. Use this approach when automatic localStorage persistence is not sufficient. For example, use manual persistence when you need to sync the workflow state to a server, store it in a database, or tie it to an existing session management system.

window.addEventListener('unqork-embed-workflow-navigate', (event) => {
  const { submissionId, stepPath } = event.detail
  // Save to your backend, sessionStorage, or database
  myBackend.saveProgress({ submissionId, stepPath })
})

Step 7: Set Up Error Handling

Embedded UI surfaces errors through a custom DOM event and an optional global callback.

Method A: DOM Event Listener

The runtime dispatches a unqork-embed-error event on window whenever an error occurs. Listening for it with addEventListener is the standard approach and works like any other browser event. Multiple listeners can be registered independently, and any listener can be removed later with removeEventListener. Use this method in most cases. It fits naturally into existing event-driven code and does not require access to the runtime instance.

window.addEventListener('unqork-embed-error', (event) => {
  const error = event.detail
  console.error(`[${error.type}] ${error.message}`)
})

Method B: Global Error Callback

Assigning a function to window.__UNQORK_EMBED_ERROR_HANDLER__ registers a global handler that the runtime calls directly, without firing a DOM event. Only one callback can be active at a time. Assigning a new function replaces the previous one. Use this method when you want one central error handler and do not need to register multiple independent handlers. This method also works when the code setting up error handling does not have access to the runtime instance.

window.__UNQORK_EMBED_ERROR_HANDLER__ = (error) => {
  console.error(`[${error.type}] ${error.message}`)
}

Method C: Runtime Error Callback

Registering a callback with runtime.onError() scopes error handling to a specific runtime instance. The callback is automatically removed when the runtime is torn down, so it does not outlive the module it covers. Use this method when you have multiple runtime instances on the same page and need to route each instance's errors to a different handler, or when you want the error handler to share the same lifecycle as the runtime.

runtime.onError((error) => {
  console.error(`[${error.type}] ${error.message}`)
})

Error Payload Shape

Every error object includes the following fields.

Field Type Description
timestamp string ISO 8601 timestamp.
type string Error category (see table below).
message string Human-readable description.
moduleId string Module ID, if applicable.
originalError any The raw error object for debugging.

Error Types

Type When Additional Fields
http An API request fails. status, url, statusText
angular An AngularJS exception is caught.
routing A UI-Router state transition fails. toState, fromState
template A template request fails.
workflow A general workflow error occurs. workflowPath, stepPath, status
workflow-fatal A fatal workflow error that prevents further navigation. workflowPath, stepPath
workflow-navigation A workflow step transition fails. workflowPath, stepPath, action (next, previous, or goto)

Try-Catch on Mount

All mount methods are asynchronous. Wrap them in try-catch to handle errors inline:

try {
  await runtime.mountModule({
    moduleId: '696f9c76f9325e1f580152c7',
    target: 'unqork-app'
  })
} catch (error) {
  console.error('Mount failed:', error.message)
  // Show fallback UI
}

Step 8: Manage the Module Lifecycle

The Embedded UI runtime provides methods for tearing down and restarting an embedded module.

Destroying the Runtime

Call destroy() to completely tear down the embedded module, clean up the DOM, and release memory.

await runtime.destroy()

destroy() performs the following:

  1. Removes the active iframe and cleans up its message listeners.
  2. Replaces the active web component with a new empty instance, clearing the Shadow DOM.
  3. Nullifies internal runtime references.
  4. Clears the module runtime cache.

Re-Mounting After Destroy

After calling destroy(), you can mount a new module or the same module again.

await runtime.destroy()

// Mount a different module
await runtime.mountModule({
  moduleId: 'different-module-id',
  target: 'unqork-app'
})

Switching Between Web Component and Iframe Mode

To switch between web component and iframe mode, call destroy() before remounting.

// Currently mounted as web component
await runtime.destroy()

// Remount as iframe
await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app',
  iframe: true
})

Recovering After an Error

Call recover() to tear down the current state and remount the last module with the same parameters. Use recover() when you want to retry the exact same mount without rewriting the mount call, like an error handler that does not have access to the original parameters. Use destroy() followed by a new mountModule() call when you need to change the module ID, target, or other options before remounting.

await runtime.recover()

recover() is equivalent to:

const savedParams = runtime._currentModuleParams
await runtime.destroy()
await runtime.mountModule(savedParams)

Step 9: Listen for Custom Events

Embedded UI dispatches these events on window throughout the module lifecycle.

Event Payload When
unqork-embed-error Error payload (see Step 7) An error occurs during any phase.
unqork-embed-mounted { moduleId } A module finishes mounting.
unqork-embed-workflow-mounted { workflowPath } A workflow finishes mounting.
unqork-embed-navigate { href } A link click inside the module is intercepted (navigation blocked).
unqork-embed-workflow-navigate { submissionId, stepPath } A workflow step transition completes.

Example: Full Event Setup

The following script registers handlers for the most commonly used lifecycle events.

window.addEventListener('unqork-embed-mounted', (e) => {
  console.log('Module mounted:', e.detail.moduleId)
})

window.addEventListener('unqork-embed-workflow-navigate', (e) => {
  const { submissionId, stepPath } = e.detail
  console.log(`Step: ${stepPath}, Submission: ${submissionId}`)
})

window.addEventListener('unqork-embed-navigate', (e) => {
  console.log('Navigation blocked. Module tried to navigate to:', e.detail.href)
})

Step 10: Apply Custom Styles

Pass a style parameter to apply a named style set from Style Administration:

await runtime.mountModule({
  moduleId: '696f9c76f9325e1f580152c7',
  target: 'unqork-app',
  style: 'my-custom-theme'
})

Complete Example: Full-Featured Setup

This example combines authentication, error handling, lifecycle management, and event listening.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Full Embedded UI Setup</title>
  <style>
    body { font-family: sans-serif; margin: 2rem; }
    .controls { margin: 1rem 0; }
    .controls button { margin-right: 0.5rem; padding: 0.5rem 1rem; }
    #error-log { color: red; margin-top: 1rem; }
  </style>
</head>
<body>

  <h1>Embedded Module</h1>

  <div class="controls">
    <button id="btn-destroy">Destroy</button>
    <button id="btn-recover">Recover</button>
    <button id="btn-remount-iframe">Remount as Iframe</button>
    <button id="btn-remount-wc">Remount as Web Component</button>
  </div>

  <unqork-app></unqork-app>

  <div id="error-log"></div>

  <a href="https://google.com" target="_blank">External Link (still works)</a>

  <script src="https://your-environment.unqork.io/embedded.js" defer></script>
  <script>
    const MODULE_ID = '696f9c76f9325e1f580152c7'
    const TARGET = 'unqork-app'

    // Error handling
    window.addEventListener('unqork-embed-error', (e) => {
      const el = document.getElementById('error-log')
      el.textContent += `[${e.detail.type}] ${e.detail.message}\n`
    })

    // Lifecycle events
    window.addEventListener('unqork-embed-mounted', (e) => {
      console.log('Mounted:', e.detail.moduleId)
    })

    window.addEventListener('unqork-embed-navigate', (e) => {
      console.log('Navigation blocked:', e.detail.href)
    })

    // Main initialization
    ;(async () => {
      const runtime = window.unqork.runtimes.default
      await runtime.initialize({ autoLogin: true })

      await runtime.mountModule({ moduleId: MODULE_ID, target: TARGET })

      // Destroy button
      document.getElementById('btn-destroy').addEventListener('click', async () => {
        await runtime.destroy()
        console.log('Destroyed')
      })

      // Recover button
      document.getElementById('btn-recover').addEventListener('click', async () => {
        await runtime.recover()
        console.log('Recovered')
      })

      // Remount as iframe
      document.getElementById('btn-remount-iframe').addEventListener('click', async () => {
        await runtime.destroy()
        await runtime.mountModule({ moduleId: MODULE_ID, target: TARGET, iframe: true })
      })

      // Remount as web component
      document.getElementById('btn-remount-wc').addEventListener('click', async () => {
        await runtime.destroy()
        await runtime.mountModule({ moduleId: MODULE_ID, target: TARGET })
      })
    })()
  </script>
</body>
</html>

Complete Example: Workflow with Persistence

The following is a complete workflow setup with automatic state persistence enabled.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Embedded Workflow</title>
</head>
<body>

  <h1>Insurance Onboarding</h1>
  <unqork-app></unqork-app>

  <script src="https://your-environment.unqork.io/embedded.js" defer></script>
  <script>
    window.addEventListener('unqork-embed-error', (e) => {
      console.error(`[${e.detail.type}] ${e.detail.message}`)
    })

    window.addEventListener('unqork-embed-workflow-navigate', (e) => {
      console.log(`Step: ${e.detail.stepPath}, Submission: ${e.detail.submissionId}`)
    })

    ;(async () => {
      const runtime = window.unqork.runtimes.default
      await runtime.initialize({ autoLogin: true })

      await runtime.mountWorkflow({
        workflowPath: 'insurance-onboarding',
        target: 'unqork-app',
        persistWorkflowState: true
      })
    })()
  </script>
</body>
</html>

Troubleshooting

Use this table to diagnose common issues when setting up an Embedded UI integration.

Symptom Likely Cause Resolution
embedded.js returns a 404 error The Embedded UI feature is not enabled on the environment. Contact your Unqork administrator to enable the feature.
CORS errors in the console The host page domain is not in the CORS allowlist. Add the full origin (including port) to Environment Administration > Cross-Origin Resource Sharing (CORS).
Module displays a loading spinner indefinitely Authentication failed silently. Check the Network tab for 401/403 responses. Use isAuthenticated() to verify.
Could not find target element The mount target does not exist in the DOM when mountModule is called. Ensure the target element exists before calling mount. Defer the script or use DOMContentLoaded.
Iframe embedding is not supported for Vega A Runtime 2.0 module was passed with iframe: true. Remove the iframe option or use a Runtime 1.0 module.
Styles leak between host page and module Using iframe mode without Shadow DOM. Switch to web component mode (default) for CSS isolation.
Unqork Runtime must be initialized mountModule or mountWorkflow called before initialize(). Always call await runtime.initialize() first.
Page navigates away when module loads Navigation guards failed. The page should not navigate away during module load. File a bug report with the console output.

Changelog

Date Change
2026-06-11 Added per-type additional fields column to the Error Types table.
2026-05-20 Initial publication.