Plugins | Widgets
Give Feedback

Examples

Updated on October 4, 2021

This guide provides some examples of functional widgets, with their schema and their source code.

⚠️ In order to view your widget on your Crisp Inbox, your plugin must be activated (even during the development phase of your widget). Make sure to set the visibility of your plugin to Private so that only you have access to the widget while developing and testing it.

Generic Widget

Widget screenshots

Example of a generic widget

Widget schema

{
  "version": "1.0",

  "sections": [
    {
      "id": "section_information",
      "title": "Information",

      "items": [
        {
          "id": "data_user_creation_date",
          "type": "data",

          "value": {
            "label": "Account creation date",

            // Empty value, in order to fetch it via the action URL
            "value": "",
            "icon": "clock",

            // Prevent edition
            "editable": false
          }
        },

        {
          "id": "data_user_id",
          "type": "data",

          "value": {
            "label": "Account ID",

            // Empty value, in order to fetch it via the action URL
            "value": "",
            "icon": "account_box",

            // Placeholder to display when the value is being edited
            "placeholder": "Enter account ID"
          }
        },

        {
          "id": "button_account",
          "type": "button",

          "value": {
            "type": "modal",
            "label": "View account",
            "color": "green",

            // Use data binding in the URL path
            "url": "https://acme.com/account/{conversation.meta.data.user_id}/"
          }
        }
      ]
    },

    {
      "id": "section_order",

      // Only show this section if the custom data `order_id` exists
      "condition": "{conversation.meta.data.order_id || conversation.contact_data.order_id}",
      "title": "Order",

      "items": [
        {
          "id": "data_order_creation_date",
          "type": "data",

          "value": {
            "label": "Order creation date",

            // Empty value, in order to fetch it via the action URL
            "value": "",
            "icon": "clock",

            // Prevent edition
            "editable": false
          }
        },

        {
          "id": "data_order_id",
          "type": "data",

          "value": {
            "label": "Order ID",

            // Display the custom data `order_id`
            "value": "{conversation.meta.data.order_id || conversation.contact_data.order_id}",
            "icon": "book"
          }
        },

        {
          "id": "button_order",
          "type": "button",

          "value": {
            "type": "modal",
            "label": "View order",

            // Customize button color
            "color": "green",

            // Use data binding in the URL path
            "url": "https://acme.com/order/{conversation.meta.data.order_id || conversation.contact_data.order_id}/"
          }
        }
      ]
    },

    {
      "id": "section_actions",
      "title": "Actions",

      "items": [
        {
          "id": "button_refund",
          "type": "button",

          "value": {
            "type": "hook",
            "label": "Refund last invoice",

            // Customize button color
            "color": "orange",

            // Embed Crisp data via data bindings
            "data": {
              "user_id": "{conversation.meta.data.user_id}",
              "order_id": "{conversation.meta.data.order_id}",
              "order_status": "{conversation.meta.data.order_status}"
            }
          }
        },

        {
          "id": "button_debug_emails",
          "type": "button",

          "value": {
            "type": "hook",
            "label": "Start emails debug",

            // Embed Crisp data via data binding
            "data": {
              "user_id": "{conversation.meta.data.user_id}"
            }
          }
        }
      ]
    },

    {
      "id": "section_form",
      "title": "Form",

      "items": [
        {
          "id": "data_user_first_name",
          "type": "data",

          "value": {
            "label": "User first name",
            "placeholder": "Enter first name...",
            "icon": "person",

            // Match group identifier
            "group": "my_form"
          }
        },

        {
          "id": "data_user_last_name",
          "type": "data",

          "value": {
            "label": "User last name",
            "placeholder": "Enter last name...",
            "icon": "person",

            // Match group identifier
            "group": "my_form"
          }
        },

        {
          "id": "button_submit",
          "type": "button",

          "value": {
            "type": "submit",
            "label": "Submit form",

            // Match group identifier
            "group": "my_form",

            // Embed Crisp data via data binding
            "data": {
              "user_id": "{conversation.meta.data.user_id}"
            }
          }
        }
      ]
    }
  ]
}

Widget server source code

const express = require("express");
const bodyParser = require("body-parser");

const app = express();
const port = 3999;

app.use(bodyParser.json());


// Triggers when a data item value is fetched
handleDataFetchAction = (body, res) => {
  console.log(`2️⃣  Received data fetch action from: ${body.widget.section_id} - ${body.widget.item_id}`);

  res.send({
    data: {
      value: "Data value"
    }
  });
}

// Triggers when a data item value is edited
handleDataEditAction = (body, res) => {
  console.log(`2️⃣  Received data edit ation from: ${body.widget.section_id} - ${body.widget.item_id}`);
  console.log(body.payload.value);

  res.send({});
}

// Triggers when a data item action is received
handleDataAction = (body, res) => {
  console.log(`\n1️⃣  Received data action from: ${body.origin.website_id} - ${body.origin.session_id}`);

  switch (body.action.method) {
    case "fetch": {
      handleDataFetchAction(body, res);

      break;
    }

    case "edit": {
      handleDataEditAction(body, res);

      break;
    }

    default: {
      res.send({});
    }
  }
}

// Triggers when a submit button is clicked
handleSubmitButtonAction = (body, res) => {
  console.log(`2️⃣  Received submit button action from: ${body.widget.section_id} - ${body.widget.item_id}`);
  console.log(body.payload.value);
  console.log(body.payload.data);

  res.send({});
}

// Triggers when a hook button is clicked
handleHookButtonAction = (body, res) => {
  console.log(`2️⃣  Received hook button action from: ${body.widget.section_id} - ${body.widget.item_id}`);
  console.log(body.payload.data);

  res.send({});
}

// Triggers when a button action is received
handleButtonAction = (body, res) => {
  console.log(`\n1️⃣  Received button action from: ${body.origin.website_id} - ${body.origin.session_id}`);

  if (body.payload.value) {
    handleSubmitButtonAction(body, res);
  } else {
    handleHookButtonAction(body, res);
  }
}

// Listens to POST requests
app.post("/", (req, res) => {
  switch (req.body.action.type) {
    case "data": {
      handleDataAction(req.body, res);

      break;
    }

    case "button": {
      handleButtonAction(req.body, res);

      break;
    }

    default: {
      res.send({});
    }
  }
});

app.listen(port, () => {
  console.log(`Widget server listening at http://localhost:${port}`);
});

iFrame Widget

Widget screenshots

Example of an iFrame widget
Example of an iFrame widget modal

Widget schema

{
  "version": "1.0",
  "url": "https://acme.com/crisp_widget.html"
}

Widget source code

<!DOCTYPE html>
  <html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script type="text/javascript" src="https://assets.crisp.chat/widget/javascripts/sdk.min.js"></script>

    <script type="text/javascript">
      // Parse query parameters
      document.addEventListener("DOMContentLoaded", function() {
        var urlSearchParams = new URLSearchParams(window.location.search);
        var params = Object.fromEntries(urlSearchParams.entries());

        var ul = document.getElementById("params");

        Object.keys(params).forEach((key) => {
          var li = document.createElement("li");

          li.appendChild(document.createTextNode(`${key}: ${params[key]}`));
          ul.appendChild(li);
        });
      });

      // Register callbacks
      $crisp.onDataAcquired = (namespace) => {
        console.log(namespace);
        console.log($crisp.data[namespace]);
      }

      $crisp.onEventForwarded = (payload) => {
        console.log(payload);
      }

      // Expose proxy methods
      function acquireData() {
        $crisp.acquireData("conversation");
      }

      function showModal() {
        $crisp.showModal("https://acme.com/crisp_widget_modal.html", "medium");
      }

      function showToast() {
        $crisp.showToast("success", "Hello from toast");
      }
    </script>

    <style>
      body,
      h3 {
        margin: 0;
        padding: 0;
      }

      .widget {
        padding: 10px;
      }
    </style>
  </head>

  <body>
    <div class="widget">
      <h3>Widget</h3></br>

      <span>Parameters:</span></br>
      <ul id="params"></ul><br>

      <button onclick="acquireData()">Acquire data</button><br><br>
      <button onclick="showModal()">Show modal</button><br><br>
      <button onclick="showToast()">Show toast</button>
    </div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script type="text/javascript" src="https://assets.crisp.chat/widget/javascripts/sdk.min.js"></script>

    <script type="text/javascript">
      // Parse query parameters
      document.addEventListener("DOMContentLoaded", function() {
        var urlSearchParams = new URLSearchParams(window.location.search);
        var params = Object.fromEntries(urlSearchParams.entries());

        var ul = document.getElementById("params");

        Object.keys(params).forEach((key) => {
          var li = document.createElement("li");

          li.appendChild(document.createTextNode(`${key}: ${params[key]}`));
          ul.appendChild(li);
        });
      });

      // Expose proxy methods
      function sendEventToWidget() {
        $crisp.forwardEvent("widget", {
          key: "value"
        });
      }

      function closeModal() {
        $crisp.closeModal();
      }
    </script>

    <style>
      body,
      h3 {
        margin: 0;
        padding: 0;
      }

      .widget {
        padding: 10px;
      }
    </style>
  </head>

  <body>
    <div class="widget">
      <h3>Widget modal</h3></br>

      <span>Parameters:</span></br>
      <ul id="params"></ul><br>

      <button onclick="sendEventToWidget()">Send event to widget</button><br><br>
      <button onclick="closeModal()">Close modal</button>
    </div>
  </body>
</html>