Use a Plugin Developed in Oracle Visual Builder

You might have created a custom plugin using Oracle Visual Builder (Visual Builder) and want to use it with Oracle Fusion Field Service. Starting with 25C, you can standardize the approach to create the plugin to ensure that it aligns with the Fusion standards.

To start creating a plugin in Visual Builder, select the Application type of screen and then build your plugin using the instructions in the Optimize Your Builds and Audit Your Code Using Grunt guide. You can use the Audit tab to make sure that the plugin is created correctly without any errors.

These are the high-level steps you must perform to prepare your plugin:

  1. Connect the Visual Builder plugin to the Oracle Fusion Field Service Plugin API.
  2. Review the requirements for the plugin archive.
  3. Export and build the plugin.
  4. Import the plugin.

Connect the Visual Builder plugin to the Oracle Fusion Field Service Plugin API

To use the Plugin API in the plugin you need to include the Oracle Fusion Field Service connector file in your plugin:

  1. Create the ofsc-connector.js file in the Resources > js folder in Visual Builder.
  2. Copy the following example code into the ofsc-connector.js file:
/**
 * @licence
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * Oracle Technology Network Developer License Terms (https://www.oracle.com/downloads/licenses/production-modify-license.html)
 */
"use strict";
define([
], () => {
    const OFSC_API_VERSION = 1;
    class OfscConnector extends EventTarget {
        constructor() {
            super();
            window.addEventListener("message", this.onPostMessage.bind(this), false);
            this._currentCommunicationCallback = null;
            this._currentCommunicationPromise = null;
        }
        /**
         * @param {Object} dataToSend
         * @returns {Promise.<*>}
         */
        sendMessage(dataToSend) {
            if (this._currentCommunicationPromise) {
                return Promise.reject(new Error('Communication channel is busy'));
            }
            this._currentCommunicationPromise = new Promise((resolve, reject) => {
                const originUrl = this.constructor._getOriginUrl();
                const origin = originUrl ? this.constructor._getOrigin(originUrl) : '*';
                this._currentCommunicationCallback = (receivedData) => {
                    this._currentCommunicationCallback = null;
                    this._currentCommunicationPromise = null;
                    if (receivedData instanceof Error) {
                        return reject(receivedData);
                    }
                    if (receivedData.method && receivedData.method === 'error') {
                        return reject(receivedData);
                    }
                    return resolve(receivedData);
                };
                dataToSend.apiVersion = OFSC_API_VERSION;
                parent.postMessage(dataToSend, origin);
                this.dispatchEvent(new CustomEvent('debugMessageSent', { detail: dataToSend }));
            });
            return this._currentCommunicationPromise;
        }
        onPostMessage(event) {
            // Ignore internal JET messages
            if (event.source === window) {
                return;
            }
            if (typeof event.data === 'undefined') {
                this.dispatchEvent(new CustomEvent('debugIncorrectMessageReceived', { detail: "No data" }));
                if (this._currentCommunicationCallback) {
                    this._currentCommunicationCallback(new Error('No data'));
                    return;
                }
                return false;
            }
            const data = this.constructor.parseJSON(event.data, null);
            if (data === null) {
                if (this._currentCommunicationCallback) {
                    this._currentCommunicationCallback(new Error('Incorrect JSON'));
                    return;
                }
                this.dispatchEvent(new CustomEvent('debugIncorrectMessageReceived', { detail: event.data }));
                return false;
            }
            this.dispatchEvent(new CustomEvent('debugMessageReceived', { detail: data }));
            if (this._currentCommunicationCallback) {
                this._currentCommunicationCallback(data);
            } else {
                this.dispatchEvent(new CustomEvent('messageFromOfs', { detail: data }));
            }
        }
        static generateCallId() {
            return window.btoa(String.fromCharCode.apply(null, window.crypto.getRandomValues(new Uint8Array(16))));
        }
        static _getOrigin(url) {
            if (typeof url === 'string' && url !== '') {
                if (url.indexOf("://") > -1) {
                    return (window.location.protocol || 'https:') + url.split('/')[2];
                } else {
                    return (window.location.protocol || 'https:') + url.split('/')[0];
                }
            }
            return '';
        }
        static _getOriginUrl() {
            if (document.referrer) {
                return document.referrer;
            }
            if  (document.location.ancestorOrigins && document.location.ancestorOrigins[0]) {
                return document.location.ancestorOrigins[0];
            }
            return null;
        }
        static parseJSON(text, defaultValue = null) {
            try {
                return JSON.parse(text);
            } catch (err) {
                return defaultValue;
            }
        }
    }
    return OfscConnector;
});
  1. Include the new file ofsc-connector.js into the plugin. This screenshot shows the ofsc-connector.js file included in the plugin:

.This screenshot shows the ofsc-connector.js file added to the plugin.

  1. Create a new Action Chain with the name initOFSConnector.

This screenshot shows adding a new action chain in Visual Builder.

  1. Add the following chain using the UI or Code tab:

This screenshot shows the initOfsConnector action chain in Visual Builder.

Add this code:

define([
  'vb/action/actionChain',
  'resources/js/ofsc-connector'
], (
  ActionChain,
  OfscConnector
) => {
  'use strict';
  class initOfsConnector extends ActionChain {
    /**
     * @param {Object} context
     */
    async run(context) {
      const { $page, $flow, $application, $constants, $variables } = context;
      const callFunction = await this.pluginStartAction(context);
    }
    /**
     * @private
     * @param {Object} context
     */
    async pluginStartAction(context) {
      const { $page, $flow, $application, $constants, $variables } = context;
      const ofscConnector = new OfscConnector();
      $application.variables.ofscConnector = ofscConnector;
      ofscConnector.sendMessage({
        method: 'ready',
        sendInitData: true
      }).then(this.onMessage.bind(this, $application));
    }
    /**
     * @private
     */
    onMessage($application, messageData) {
      if (!messageData) {
        return;
      }
      switch (messageData.method) {
        case 'init':
          this.initPlugin($application, messageData);
          break;
        case 'open':
          this.openPlugin($application, messageData);
          break;
        // default:
        //   console.log('method', messageData.method, 'data', messageData);
      }
    }
    /**
     * @private
     */
    initPlugin($application, jsonData) {
      this.sendInitEnd($application.variables.ofscConnector);
    }
    /**
     * @private
     */
    sendInitEnd(ofscConnector) {
      ofscConnector.sendMessage({ method: 'initEnd' });
    }
    /**
     * @private
     */
    openPlugin($application, jsonData) {
      this.setUserLocale(jsonData.user);
    }
    /**
     * @private
     */
    setUserLocale(user) {
      const currentLocale = window.localStorage.getItem('ofs_plugin_locale');
      const locale = user.locale || user.languageCode || window.navigator.language;
      if (currentLocale !== locale) {
        window.localStorage.setItem('ofs_plugin_locale', locale);
        window.location.href = this.getPluginUrl();
      }
    }
    /**
     * @private
     */
    getPluginUrl() {
      const url = window.location.href;
      if (url.endsWith('/')) {
        return url + 'index.html';
      }
      return url;
    }
  }
  return initOfsConnector;
});


Review the Requirements for the Plugin Archive

This section includes the requirements to create the plugin archive, such as the file format and supported file types.

Archive file format
The archive must have .zip extension and be a ZIP archive. The size of the archive must not exceed 5 MB.

Archive content

  • The archive must contain only directories and these file types:
    • .html files
    • .css files
    • .js, .json, .map files
    • .appache files
    • .jpg, .jpeg, .png, .gif, .svg, .ico files
    • Directories
  • The total size of all files must not exceed 20 MB.
  • The index.html file must be located 
    • in the /build/optimized/webApps/<plugin_name_folder>/index.html for an optimized Visual Builder plugin
    • in the / (root folder) of the archive for a hosted plugin 
  • The archive must contain not more than 600 entries, including empty directories. 

This screenshot shows the requirements for the plugin archive:

This screenshot shows the requirements for the plugin archive.

Export and Build the Plugin

After you create the plugin and review the requirements for plugin archive, you must export the plugin.

Click Export on the action menu at the top right of the menu. A .zip file containing the plugin archive is created.

Build the plugin using these instructions:

  1. Install Node.js.
  2. Unzip the archive with the plugin to any local folder on your computer.
  3. Open the terminal and select the folder with the unzipped archive.
  4. Use the following terminal command to build the plugin.
npm i && grunt vb-clean && grunt vb-process-local --url:ce=http://exchange.oraclecorp.com/api/0.2.0 && grunt vb-package

For more information on how to build a plugin using Visual Builder, see the Optimize Your Builds and Audit Your Code Using Grunt guide.

  1. Zip all files and folders to a new archive. Ensure that the archive contains all plugin files and folders in the root of the archive.

Import the Plugin to Oracle Fusion Field Service
After you export and build the plugin you have created using Visual Builder, you must import it to Oracle Fusion Field Service.

  1. Sign in to Oracle Fusion Field Service. 
  2. Click Configuration > Forms & Plugins.
  3. Click Add Plugin and select 'Plugin Archive'. 
  4. Upload the your plugin archive.
  5. Click Add. The Visual Builder plugin archive is added as a hosted plugin.

Let’s say you want to enhance the existing plugin, but you don't have access to the source code in Visual Builder. Follow these steps:

  1. Open the Edit Plugin page in Oracle Fusion Field Service. 
  2. Go to the Version history section and download the archive. 
  3. Import it back to Visual Builder. Your plugin is available for modification.

Create an Oracle Visual Builder Plugin that Supports Multiple Languages

Starting with update 25C, you can also create a plugin in Visual Builder that supports multiple languages. Multi-language is natively supported in Visual Builder, which allows you to build and maintain a single plugin for users speaking different languages. For more information on how to do this, see Create a Multi-language Application in Oracle Visual Builder: Create a Language Switcher.

Add the ofsConnector.js and ofsInitConnector.js files into your plugin as described in the Build Your Application Using Oracle Visual Builder Studio section.

Add the following code to the ofsInitConnector file:

setUserLocale(user) {
      const currentLocale = window.localStorage.getItem('ofs_plugin_locale');
      const locale = user.locale || user.languageCode || window.navigator.language;
      if (currentLocale !== locale) {
        window.localStorage.setItem('ofs_plugin_locale', locale);
        window.location.href = this.getPluginUrl();
      }
    }

This code applies a new language to the plugin. 

Plugin API

A new locale field is sent through the Plugin API on the init message and the field belongs to the user collection. You can use it to apply the language selected in the FFS UI to the plugin.

init message
{
...,
"user": {
        "languageCode": "en",
        "locale": "en-US"
    }
}

Business Benefits

  • Simplified implementation of plugins created in Visual Builder by following the official Fusion standards
  • Decreased development and maintenance efforts of plugins for customers having workforce speaking in different languages 

Steps to Enable

You don't need to do anything to enable this feature.

Tips And Considerations

You can't upload non-optimized plugin archives to Oracle Fusion Field Service.