SuiteCRM : add global JavaScript

In SuiteCRM, there may be a need to add JavaScript globally. In my case, I frequently want to send actions to an external automation application (n8n) to accomplish various tasks for my clients.

For example:

  • Creating orders in Woo;
  • Updating invoices;
  • Sending vCards via email;
  • Generating formatted Excel or PDF files;
  • Exporting data in any format;

Therefore, I want my JavaScript function tmcWebhook() to be available throughout the entire application, making the best use of SuiteCRM logic. The benefit is that my integration remains stable during updates. This approach also ensures that controls on access to PHP scripts are the same as elsewhere in SuiteCRM.

Note: all files are in the custom directory at the root of the installation. This ensures that the core files of SuiteCRM are not modified and can survive updates.

This approach works on SuiteCRM 7.x — I haven’t tested it on SuiteCRM 8.x yet, and the migration will likely need to be done by the end of 2024.

Define a Logic Hook to Load the JavaScript Function

First, you need to add a hook in

custom/modules/logic_hooks.php
$hook_array['after_ui_frame'][] = array(
  1,
  'Adds TMC JavaScript for Webhook capability',  //Label
  'custom/include/tmcGlobal/tmcGlobal.php', //Include file
  'tmcHooks', //Class
  'load_TMC_JS' //Method
);

With definitions in:

custom/include/tmcGlobal/tmcGlobal.php
<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

class tmcHooks{

  function load_TMC_JS($event,$arguments){
    global $sugar_config;

      if( (!isset($_REQUEST['sugar_body_only']) || $_REQUEST['sugar_body_only']!=true) && $_REQUEST['action']!='modulelistmenu' && $_REQUEST['action']!='Popup' && empty($_REQUEST['to_pdf']) && ( !empty($_REQUEST['module']) && $_REQUEST['module']!='ModuleBuilder') && empty($_REQUEST['to_csv'])){
        echo '<script type="text/javascript" src="custom/include/tmcGlobal/tmcWebhook.js"></script>';
        }
      }
    }
?>

Finally, I put the script I want to make available throughout my application in:

custom/include/tmcGlobal/tmcWebhook.js
function tmcWebhook(server, user, record, action) {

    // ...
    // your logic here
    // ...

    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'index.php?entryPoint=tmcWebhook', true);
    xhr.setRequestHeader('Content-Type', 'application/json');

    // ...
    // your logic here
    // ...
}

This JavaScript function tmcWebhook() will now be available in every module to call the tmcWebhook Entry Point and pass it data (server, user, record, action). So, you need to define this Entry Point.

Define the Entry Point

Defining the tmcWebhook Entry Point allows you to call it from the function and secure its access. I do this by creating the following file:

custom/Extension/application/Ext/EntryPointRegistry/tmcWebhook.php
<?php

$entry_point_registry['tmcWebhook'] = array(
    'file' => 'custom/include/tmcGlobal/tmcWebhook.php',
    'auth' => true,
);

I then create the tmcWebhook.php file that receives requests:

custom/include/tmcGlobal/tmcWebhook.php
<?php

if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

// getting raw post data

$rawPostData = file_get_contents("php://input");
$postData = json_decode($rawPostData, true);

// Get POST variables
$server = $postData['server'] ?? '';
$user = $postData['user'] ?? '';
$record = $postData['record'] ?? '';
$action = $postData['action'] ?? '';

// ...

// ... rest of script

// ...

?>

Once these files are in place, you need to run “Quick Repair and Rebuild” in the admin panel to apply the changes (Entry Point and Hook).

Now, the tmcWebhook() JavaScript function is available everywhere in SuiteCRM. In another post, I provide two examples of how to use it:

  • Add a button to a side menu;
  • Add a button to the actions menu in the detailed view.

There are other methods to include JavaScript on specific pages (for example, only on the display of a Contact record), which can be more efficient for more specific applications. In this case, it’s a very lightweight and versatile function that I use in various places. This is why I chose a very global approach.