Callback custom action workflow

Callback custom action workflow

ThoughtSpot allows you to create custom actions to push data to a third-party application from the ThoughtSpot application context. You can configure this custom menu item to initiate an action or a callback to the parent application. For example, if you have embedded the ThoughtSpot search functionality in your application, you can add an action to send the data obtained from a search query to an external application or a workflow.

The data is available to the external application in the JavaScript Object Notation (JSON) format. You must parse the JSON data to get the values you need using JavaScript in the receiving application.

Push data using a callback custom action🔗

To push data to an external app through a callback custom action, follow these steps:

Add a callback custom action in the ThoughtSpot🔗

You can Create a callback custom action in the ThoughtSpot Developer portal (Develop > Customization > Custom actions > Create action) or using the /tspublic/v1/admin/embed/actions REST API.

Register the callback through the Visual Embed SDK🔗

Register the callback using the Visual Embed SDK. This configuration is required to trigger an event when the custom action is initiated.

The following example shows how to call showData when a custom action is received.

const searchEmbed = new SearchEmbed('#embed', {
    frameParams: {},
    .on(EmbedEvent.CustomAction, (payload) => {

Initiate the callback action🔗

Define the function and classes to handle the data that you want to send as a payload when a custom action is triggered. This example shows how to handle the show-data callback custom action and the details of its data:

const showData = (payload) => {
    const data =;
    if ( === 'show-data') {
        // Display the data as a table.
        const actionData = ActionData.createFromJSON(payload);
        const html = actionDataToHTML(actionData);
        const dataContentElement = document.getElementById('modal-data-content');
        dataContentElement.innerHTML = html;
        const dataElement = document.getElementById('show-data'); = 'block';
    } else {
        console.log(`Got unknown custom actions ${}`);

ThoughtSpot visualization objects can include several rows of records. For callback custom action payloads, the Visual Embed SDK supports retrieving data in batches.

The following example shows how to handle large datasets in custom action payloads:

const data =;
if ( === 'show-data') {
    const fetchAnswerData = await payload.answerService.fetchData(1, 5); //where the first integer is the offset value and second integer is batchsize
    console.log('fetchAnswerData:::', fetchAnswerData);

Define functions and classes to handle custom action data🔗

The following code snippet shows the sample classes and functions for handling custom action data. For an up-to-date version of the code sample, see dataclasses.js.

const zip = (arrays) => {
    // combines and inverts arrays, so a = [1, 2, 3], b = [4, 5, 6] becomes [[1,4], [2,5], [3,6]]
    return arrays[0].map(function(_, i) {
        return {
            return array[i]
class ActionData {
    // Wrapper for the data sent when a custom action is triggered.
    constructor() {
        this._columnNames = []; // list of the columns in order.
        this._data = {}; // data is stored and indexed by column with the index being column name.
    get nbrRows() {
        // Returns the number of rows.  Assumes all columns are of the same length.
        if (this._columnNames && Object.keys(this._data)) { // make sure there is some data.
            return this._data[this._columnNames[0]]?.length;
        return 0;
    get nbrColumns() {
        // Returns the number of columns.
        return this._columnNames.length;
    static createFromJSON(jsonData) {
        // Creates a new ActionData object from JSON.
        const actionData = new ActionData();
        // Gets the column names.
        const nbrCols =;
        for (let colCnt = 0; colCnt < nbrCols; colCnt += 1) {
        let dataSet;
        dataSet = (Array.isArray( ?
  [0].columnDataLite :
        for (let colCnt = 0; colCnt < actionData.nbrColumns; colCnt++) {
            actionData._data[actionData._columnNames[colCnt]] = Array.from(dataSet[colCnt].dataValue); // shallow copy the data
        return actionData
    getDataAsTable() {
        // returns the data as a table.  The columns will be in the same order as the column headers.
        const arrays = []
        for (const cname of this._columnNames) {
        return zip(arrays); // returns a two dimensional data array
const actionDataToHTML = (actionData) => {
    // Converts an ActionData data to an HTML table.
    let table = '<table class="tabular-data">';
    // Add a header
    table += '<tr>';
    for (const columnName of actionData._columnNames) {
        table += `<th class="tabular-data-th">${columnName}</th>`;
    table += '</tr>';
    const data = actionData.getDataAsTable();
    for (let rnbr = 0; rnbr < actionData.nbrRows; rnbr++) {
        table += '<tr>';
        for (let cnbr = 0; cnbr < actionData.nbrColumns; cnbr++) {
            table += `<td class="tabular-data">${data[rnbr][cnbr]}</td>`;
        table += '</tr>';
    table += '</table>';
    return table;
export {

For sample response payloads, see Custom action response payload.