Articles in this section

Solve Widget UI Guide for Developers

Here is a comprehensive guide for all Forethought widget UI-related issues. Whether you are a developer or just curious, we are here to help!

Note: The widget's default states are managed through Forethought. If you would like to vary from default behavior or customize settings that are not changeable in the dashboard, your team will be responsible for maintenance.

 

Script Attributes and Values

  • data-api-key (UUID, Forethought provided)
    • the required unique org authentication
  • data-intent-title (string, default is absent)
    • used to Display the workflows title
  • initial-intent-id (string)
    • conversation will start the specific workflow ID put here
  • initial-query (string)

    • initialize conversation with a specific query

  • full-screen (true | false, default is false)
    • controls if the widget automatically opens on load
  • hidden (non-empty string, default is absent)
    • when a string is present widget will hide on load
  • emit-tracking-events (true | false, default is false)
    • Will make Solve UI Widget emit tracking events that you can subscribe to via window.addEventListener('message', callback)
    •  

Aesthetic Attributes

config-ft-theme-color - string

Overrides the theme color. Example - #7b33fb

config-ft-agent-avatar-image - string

Overrides the image URL to be used as the avatar image next to messages.

config-ft-widget-header-image - string

Overrides the image URL to be used as the header image.

config-ft-widget-button-image - string

Overrides the image URL to be used as the widget button when the widget is closed.

config-ft-widget-header-title - string

Overrides the header title.

config-ft-greeting-message - string

Overrides the initial greeting message when the widget is open.

config-ft-proactive-prompt-greeting-message - string

Overrides the proactive prompt greeting message. Does nothing if proactive prompt is not enabled.

config-ft-iframe-title - string - Sets the title of the iframe in Solve UI, improving accessibility for screen reader users.

config-ft-hide-nav - string - true | false (default)

Hides the widget header.

config-ft-launch-style - string - icon (default) | floating_bar

Overrides the launch style of the widget.

config-ft-placement - string - bottom_right | bottom_left | bottom | top | middle

Overrides the placement of the widget. Icon launch style only supports bottom_right (default) and bottom_left, and floating bar launch style only supports top, middle, and bottom (default).

config-ft-size - string - standard (default) | large

Overrides the default size of the widget.

config-ft-user-message-bubble-color - string

Overrides the bubble color of the user messages. Example - #7b33fb

 

Widget Position Customizations

By default, the widget is placed at 20px away from the bottom right corner

 

Screen_Shot_2023-03-03_at_2.41.59_PM.png

We can customize it with placement attributes however:

  • position-x (left | right)
  • position-y (left | right)
  • offset-x (/^[-]?\d+(px|rem)$/)
  • offset-y (/^[-]?\d+(px|rem)$/)
  • position-absolute (true | false, default is false)

    • Controls the positioning behavior of the widget iframe:

      • Default (false): Uses fixed positioning—meaning the widget stays in place on the screen even when the user scrolls.

      • If true: Uses absolute positioning relative to the <body> element and the offset-x / offset-y values you've defined. The widget will move with the page when the user scrolls.

Note: Using position-x="left" and/or position-y="top" along with Proactive Bubbles is not supported for all widget placements (UI will look weird).

Example Embedding Script:

<script
  src={etc}
  type='application/javascript'
  data-api-key={lalalala}
  position-y='top'
  offset-y='5rem'
  position-x='left'
  offset-x='80px'
></script>

 

Persistence

  • Chat and widget open-close state persistence is managed on the Forethought side.
  • State Widget placement and view persistence are managed on your development side.

 

Changing Widget Behavior with Javascript API

By default, the Forethought widget will be displayed on any page where the embed snippet is present. You can change this and other behavior using the Javascript functions below.

type Forethought = (
  selector: 'widget',
  action: 'open' | 'close' | 'hide' | 'show' | 'triggerEventComplete',
  data?: {
    identifier?: string;
    payload?: Record<string, unknown>;
  },
) => void;

declare global {
  interface Window {
    Forethought: Forethought;
  }
  • window.Forethought('widget', 'open') - open Solve UI Widget
  • window.Forethought('widget', 'close') - close Solve UI Widget
  • window.Forethought('widget', 'hide') - hide Solve UI iframe
  • window.Forethought('widget', 'show') - show Solve UI iframe
  • window.Forethought('widget', 'triggerEventComplete', {identifier: 'trigger_event', payload: {'another cv': 'my updated context value'}}) - used to continue the conversation after a trigger event step which comes from a trigger event action
  • window.Forethought('widget', 'showProactivePrompt') - show proactive prompt (ignores `config-ft-disable-proactive-prompt`, does nothing if proactive prompt is disabled in widget configuration)
  • window.Forethought('widget', 'hideProactivePrompt') - hide proactive prompt (does nothing if proactive prompt is disabled in widget configuration)

 

Add Script Dynamically to the DOM

The snippet can also be inserted into DOM dynamically (e.g. only on pages that need it) and removed by id (id of the iframe that holds the widget is forethought-chat). An example of adding it dynamically is below:

// Remove old widget, if it exists:
document.querySelector("#forethought-chat")?.remove();

const script = document.createElement("script");
script.setAttribute("src", "https://solve-widget.forethought.ai/embed.js");
script.setAttribute("id", "forethought-widget-embed-script");
script.setAttribute("data-api-key", "Insert API key here");
script.setAttribute("data-ft-foo", "bar"); // optional
document.head.append(script);

 

Updating Conversation Context

  • config-ft-ignore-and-update-persistence-parameters - pipe-delimited string 
    • When the page is loaded and new values are provided for these parameters, Forethought will not start a new conversation and the new values will be updated in the conversation's context
    • Example: `config-ft-ignore-persistence-parameters="data-ft-location|data-ft-auth"`
    • Note: These values should be provided on page load as removing and reloading the script during the same session can cause unexpected behaviors
  • window.Forethought(’widget’, ‘updateConversationContext’, {<context_variable_id>: <new_value>, ...})
    • API for updating the conversation’s context the next time the conversation is updated (any new user input / restart).
    • To locate you `Context Variable ID`, there is a copy id to clipboard icon in the dashboard where Context Variable's are managed

Screenshot 2025-05-21 at 10.05.04 AM.png

    • Note: all separate calls to this will be aggregated during the next conversation update

 

Mobile SDK Installation

The recommended mobile solution is to use our Android and iOS SDKs found here. Installation and Usage guides can be found there.

For more information, see SDK Installation.

 

Mobile SDK Testing on Desktop

To test your mobile SDK widget on desktop, you can use a URL of the following format:

https://solve-widget.forethought.ai/?config-ft-mobile-sdk=true&full-screen=true&v=2&data-api-key=...

Just make sure to add your data-api-key.

You can open the URL in any desktop browser, and have the same experience as in your mobile app.

You can also pass data parameters as query parameters using data-ft-* prefix, and config parameters using config-ft-* prefix, for example:

data-ft-usertype=paid&config-ft-greeting-message=Hello%20there!

Note: chat handoffs that use the two-widget experience don't work in this mode.

 

Manipulate Widget Behavior 

Passing Custom Parameters

To pass parameters that can afterwards be used in conditions that determine behavior, add an attribute prefixed with data-ft- to the script tag.

In the example below, we"ve added a usertype parameter to the script.

<script
src="https://solve-widget.forethought.ai/embed.js"
id="forethought-widget-embed-script"
data-api-key="Insert API key here"
data-ft-usertype="paid"
></script>

There are a few ways your engineering team can get necessary values to pass in - take them from the page contents (e.g. if user email is specified in the profile section on the page), by making an API call to one of your services and using the response value, or by taking relevant info from the window object if it is there.

The attributes are all strings, so if you"re looking to pass a list of values, pass it as a delimiter-concatenated string of “|” (e.g.  for [1,2,3] pass “1|2|3”)

Additional Attributes

  • config-ft-theme-color - string - Overrides the theme color. Example - "#7b33fb"
  • config-ft-agent-avatar-image - string - Overrides the image URL to be used as the avatar image next to messages.
  • config-ft-widget-header-image - string - Overrides the image URL to be used as the header image.
  • config-ft-widget-button-image - string - Overrides the image URL to be used as the widget button when the widget is closed.
  • config-ft-widget-header-title - string - Overrides the header title.
  • config-ft-greeting-message - string - Overrides the initial greeting message when the widget is open.
  • config-ft-disable-proactive-prompt - string - "true" - Overrides the visibility of the proactive prompt. Does nothing if proactive prompt is not enabled.
  • config-ft-proactive-prompt-greeting-message - string - Overrides the proactive prompt greeting message. Does nothing if proactive prompt is not enabled.
  • config-ft-hide-nav - string - "true" | "false" (default) - Hides the widget header.
  • config-ft-disable-close - string - "true" | "false" (default) - Hides the "Minimize chat" button in the chat header.

  • config-ft-disable-x-button - string - "true" | "false" (default) - Hides the "Close chat" button in the chat header.
  • config-ft-banner-image-enabled - string - "true" | "false" (default)- Overrides whether the banner image is enabled or not.

  • config-ft-banner-image - string - Image URL. Overrides the banner image if that is enabled.

  • config-ft-banner-image-alt-text - string - Overrides the banner image alt text.

  • config-ft-banner-image-link - string - Overrides the banner redirect URL.

  • config-ft-show-when-conversation-starts - string - "true" | "false" (default) - Overrides whether the privacy consent should be shown when conversation starts

  • config-ft-privacy-policy - string - Overrides the privacy policy shown in the privacy consent modal

  • config-ft-prompt-header - string - Overrides the prompt header of the privacy consent modal

  • config-ft-call-to-action-label - string - Overrides the call to action label of the privacy consent modal

  • hide-intercom-widget - string - "true" (default) | "false" - when false, Intercom will not be hidden on page load.

  • emit-tracking-events - "true" | "false" (default) - Will make Solve UI Widget emit tracking events that you can subscribe to via `window.addEventListener('message', callback)`.

  • config-ft-persistence-id - string - Creates a separate persistence store for this `config-ft-persistence-id`

    • Example: use `config-ft-persistence-id="my-persistence-id-1"` in one snippet and `config-ft-persistence-id="my-persistence-id-2"` in another to have 2 separate persisted widgets.

    • Note: if you use `config-ft-persistence-id`, it disables the default
      `data-ft-workflow-tag` persistence behavior. So if you need to also have
      separate persistence stores for different workflow tags, you need to make your
      workflow tags be a part of `config-ft-persistence-id` value, e. g. `config-ft-persistence-id="my-persistence-id-1 workflow-tag-1 workflow-tag-2"`

  • config-ft-ignore-persistence-parameters - pipe-delimited string - Do not reset widget when specified embed script attributes change
    • Example: `config-ft-ignore-persistence-parameters="data-ft-location|data-ft-auth"`
  • config-ft-ignore-and-update-persistence-parameters - pipe-delimited string - Do not reset widget when specified embed script attributes change BUT also update those values in the conversation context when the page loads with new values

    • Similar to `config-ft-ignore-persistence-parameters` but if these script parameters
      values change on page load then the conversation's context will be updated with the
      new values.

    • Example: `config-ft-ignore-persistence-parameters="data-ft-location|data-ft-auth"`
  • config-ft-iframe-title - string, default: "Virtual Assistant Chat" - Value for iframe's `title` attribute, used for accessibility
  • auto-open-zd - "true" (default) | "false" - When false, Forethought will show the Zendesk widget if a chat is active on page load but will not open the Zendesk widget.
  • config-ft-intro-title-enabled - string - "true" | "false" (default) - Enables or disables the intro title text. 

  • config-ft-intro-title - string - Overrides the intro title text. 

  • config-ft-intro-subtitle-enabled - string - "true" | "false" (default) - Enables or disables the intro subtitle text. 

  • config-ft-intro-subtitle - string - Overrides the intro subtitle text. 

  • config-ft-intro-image-enabled - string - "true" | "false" (default) - Enables or disables the intro image. 

  • config-ft-intro-image - string - Overrides the intro image URL.

  • data-workflow-version (string, default is absent) - which version of the workflows are live
  • data-builder-preview (true | false, default is false) - Specifies if this widget is showing a preview or is live (if actions / hand-off should really execute)
  • data-is-model-training (true | false, default is false) - Indicated if the workflow routing model is still training or not
  • hide-ft-after-zd - true or false (default) - Will keep Solve UI Widget hidden after a Zendesk chat is ended.

Display Script Parameters

  • full-screen - "true" | "false" (default)
    • When true, the widget will open automatically on load.
  • hidden - non-empty string, default: absent
    • When set to a non-empty string, the widget iframe will get hidden automatically on load.

Position Script Parameters

  • position-x - left or right (default)
  • position-y - top or bottom (default)
  • offset-x - horizontal offset - default 20px (can be px or rem)
  • offset-y - vertical offset - default 20px (can be px or rem)
  • position-absolute - position the widget absolutely instead of fixed - (true | false, default is false)

Parameters Interacting with Forethought Widget

By default, the Forethought widget will be displayed on any page where the embed snippet is present. You can change this and other behavior using the Javascript functions below.

  • window.Forethought("widget", "open") - open Solve UI Widget - picture below
  • window.Forethought("widget", "close") - close Solve UI Widget - picture below
  • window.Forethought("widget", "hide") - hide Solve UI iframe
  • window.Forethought("widget", "show") - show Solve UI iframe
  • window.Forethought('widget', 'triggerEventComplete', {identifier: 'trigger_event', payload: {'another cv': 'my updated context value'}}) - used to continue the conversation after a trigger event step which comes from a trigger event action
  • window.Forethought('widget', 'clearLocalData') - clear data in localStorage (useful for logging out)
  • window.Forethought('widget', 'launchQuery', {payload: {query: string}}) - clears data and initializes a new conversation with provided query
Screen_Shot_2022-03-04_at_2.21.36_PM.png Screen_Shot_2022-03-04_at_2.22.05_PM.png
Opened  Closed


If you want to make one of these calls on page load, please subscribe to the forethoughtWidgetLoaded event, and make your call when the event is received, for example:

 class="c-mrkdwn__pre" data-stringify-type="pre"window.addEventListener('message', (event) => {
  if (event.data.event === 'forethoughtWidgetLoaded') {
    Forethought("widget", "open")
  }
});

 

List of Solve Widget events

// Fired when Solve UI Widget iframe gets hidden:
interface HideSolveWidgetEvent extends MessageEvent {
  data: {
    event: 'hideSolveWidget';
  };
}

// Fired when Solve UI Widget iframe gets shown:
interface ShowSolveWidgetEvent extends MessageEvent {
  data: {
    event: 'showSolveWidget';
  };
}

// Hides Solve UI iframe, does NOT get fired by Solve UI:
interface HideIframeWidgetEvent extends MessageEvent {
  data: {
    event: 'hideIframeWidget';
  };
}

// Shows Solve UI iframe, does NOT get fired by Solve UI:
interface ShowIframeWidgetEvent extends MessageEvent {
  data: {
    event: 'showIframeWidget';
  };
}

// Fired when the chat gets opened:
interface ForethoughtWidgetOpenedEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetOpened';
  };
}

// Fired when the chat gets closed:
interface ForethoughtWidgetClosedEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetClosed';
  };
}

// Fired when Solve UI Widget starts initializing:
interface ForethoughtWidgetStartedInitializingEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetStartedInitializing';
  };
}

// Fired when Solve UI Widget gets loaded (initialized):
interface ForethoughtWidgetLoadedEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetLoaded';
    /**
     * Needed to not hide forethought when zendesk widget
     * is on a page and we are in a one chat
     */
    isLiveChatMode: boolean;
    // `true` if chat is open on load, `false` if closed:
    isOpen: boolean;
  };
}

// Fired when Solve UI Widget gets loaded (initialized):
interface SolveWidgetLoadedEvent extends MessageEvent {
  data: {
    event: 'solveWidgetLoaded';
  };
}

// Fired when Solve UI Widget gets closed:
interface SolveWidgetClosedEvent extends MessageEvent {
  data: {
    event: 'solveWidgetClosed';
  };
}

// Fired when Solve UI Widget iframe gets resized (only when the chat is
// closed):
interface ForethoughtWidgetResizeEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetResize';
    dimensions: {
      height: string;
      width: string;
    };
  };
}

// Fired when Solve UI Widget iframe gets resized to maximum values:
interface ForethoughtWidgetResizeToMaximumValuesEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetResizeToMaximumValues';
  };
}

// Fired when performing a handoff to UJET:
interface ForethoughtWidgetHandoffEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetHandoff';
    ticketId: string;
  };
}

// Fired when performing a generic handoff:
interface ForethoughtWidgetGenericHandoffEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetGenericHandoff';
  };
}

// Fired when performing a handoff:
interface ForethoughtWidgetIntegrationHandoffEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetIntegrationHandoff';
    additionalParameters?: Record<string, unknown="unknown">;
    department?: string;
    email: string;
    featureFlags?: string[];
    integration: HandoffIntegrations;
    name: string;
    question: string;
  };
}

/**
 * Fired when a handoff is completed. This does not mean
 * the customer's handoff conversation is completed.
 *
 * success contains whether we were successful or not
 */
export interface ForethoughtWidgetIntegrationHandoffCompletedEvent
  extends MessageEvent {
  data: {
    event: 'forethoughtWidgetIntegrationHandoffCompleted';
    handoffData: HandoffData;
    success: boolean;
  };
}

// Fired when performing a generic handoff:
interface ForethoughtWidgetGenericIntegrationHandoffEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetIntegrationHandoff';
    additionalParameters: Record<string, unknown="unknown">;
    integration: HandoffIntegrations;
    widgetOption: GenericHandoffWidgetOption;
  };
}

// Fired when widget is in preview mode, and receives preview_logs from the server for
// conversation related requests
interface ForethoughtWidgetPreviewLogsEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetPreviewLogsEvent';
    previewLogs?: PreviewLog[];
    conversationId?: string;
  };
}

// Fired when widget is in preview mode, and the restart chat button is clicked
interface ForethoughtWidgetPreviewRestartEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetPreviewRestartEvent';
  };
}

// Fired when tracking event gets sent (it's an opt-in event, it has to be
// enabled explicitly via `emit-tracking-events="true"` embed script attribute):
interface ForethoughtWidgetTrackingEvent extends MessageEvent {
  data: {
    event: 'forethoughtWidgetTrackingEvent';
    conversation_id: string | undefined;
  } & (
    | {
        event_type: 'widget-opened';
        mechanism:
          | 'manual-click-on-widget-icon'
          | 'manual-click-on-proactive-greeting'
          | 'manual-click-on-proactive-intent-button'
          | 'js-api-widget-open'
          | 'js-api-widget-launchQuery'
          | 'full-screen-true';
      }
    | {
        event_type: 'widget-customer-ask-question';
      }
    | {
        event_type: 'widget-customer-closed-proactive-bubbles';
      }
    | {
        event_type: 'widget-customer-proactive-bubbles-shown';
      }
    | {
        doc_id: string;
        doc_index: number;
        event_type: 'go-to-article';
      }
    | {
        event_type: 'widget-customer-entered-intent-by-proactive-bubble';
        intent_id?: string;
        workflow_id?: string;
      }
    | {
        event_type: 'widget-customer-entered-intent-by-button';
        intent_id?: string;
        workflow_id?: string;
      }
    | {
        event_type: 'widget-customer-agent-chat';
        intent_id?: string;
        workflow_id?: string;
      }
    | {
        event_type: 'widget-customer-article-suggestion-helped';
        intent_id?: string;
        workflow_id?: string;
      }
    | {
        event_type: 'widget-customer-article-suggestion-unhelpful';
        intent_id?: string;
        workflow_id?: string;
      }
| {
event_type: 'widget-banner-image-clicked';
}
| {
event_type: 'widget-banner-image-closed';
} ); } // Fired by trigger event step interface ForethoughtWidgetTriggerEvent extends MessageEvent { data: { additionalContext: TriggerEventStepAdditionalContext; event: 'forethoughtTriggerEventAction'; expectedContextVariables: string[]; name: string; }; } interface ForethoughtWidgetViewImageEvent extends MessageEvent { data: { event: 'forethoughtWidgetViewImage'; open: boolean; }; } interface ForethoughtWidgetErrorEvent extends MessageEvent { data: { error: string; event: 'forethoughtWidgetError'; }; } type ForethoughtEvent = | HideSolveWidgetEvent | ShowSolveWidgetEvent | HideIframeWidgetEvent | ShowIframeWidgetEvent | ForethoughtWidgetOpenedEvent | ForethoughtWidgetClosedEvent | ForethoughtWidgetStartedInitializingEvent | ForethoughtWidgetLoadedEvent | SolveWidgetLoadedEvent | SolveWidgetClosedEvent | ForethoughtWidgetResizeEvent | ForethoughtWidgetResizeToMaximumValuesEvent | ForethoughtWidgetHandoffEvent | ForethoughtWidgetGenericHandoffEvent | ForethoughtWidgetIntegrationHandoffEvent | ForethoughtWidgetGenericIntegrationHandoffEvent | ForethoughtWidgetPreviewLogsEvent | ForethoughtWidgetPreviewRestartEvent | ForethoughtWidgetTrackingEvent | ForethoughtWidgetTriggerEvent | ForethoughtWidgetViewImageEvent | ForethoughtWidgetErrorEvent | ForethoughtWidgetIntegrationHandoffCompletedEvent; // Usage example: window.addEventListener('message', (event: ForethoughtEvent) = { if (event.data.event === '...') { // ... } });

 

Hide the Forethought Solve Widget from Unauthenticated Users

To make sure only signed-in users see the Forethought widget, wrap the embed snippet in a simple if-else block like this:

// Widget embed script: only show to logged-in Zendesk users
{{#if signed_in}}
  <script
    src="https://solve-widget.forethought.ai/embed.js"
    type="application/javascript"
    data-api-key="Insert Api Token Here"
    ></script>
{{else}}
{{/if}}

 

Content Security Policy 

Content Security Policy (CSP) is used to prevent execution of unauthorized scripts. If a parent page uses CSP, it must include the following directives:

    • script-src: https://solve-widget.forethought.ai/embed.js
    • frame-src: https://solve-widget.forethought.ai/
    • style-src: 'unsafe-inline'

Using Nonces 

If you have strict security standards and you don't allow 'unsafe-inline' directives, you can use nonces as an alternative. To implement this, you should add 'nonce-<random_key>' to both script-src and style-src CSP directives:

script-src https://solve-widget.forethought.ai/embed.js 'nonce-';
frame-src https://solve-widget.forethought.ai/;
style-src 'nonce-';

Then, add nonce="<random_key>" to the Forethought embed script.

Important: You need to ensure that <random_key> is cryptographically secure and unique for each page load.

Was this article helpful?
1 out of 1 found this helpful

Comments

0 comments

Article is closed for comments.

Support

  • Need help?

    Click here to submit a support request. We are here to assist you.

  • Business hours

    Monday to Friday 8am - 5pm PST excluding US holidays

  • Contact us

    support@forethought.ai