Skip to content

Rendering & DOM

Understanding where your widget code runs is essential for writing widgets that work correctly. This page explains the widget rendering environment, how to access your widget's DOM elements, and how to handle multiple widget instances on the same page.

Overview

Widget HTML, CSS, and JavaScript run inside a <gs-cc-registry-widget> custom element that uses Shadow DOM encapsulation. Your widget's markup and styles are isolated from the host page and from other widgets.

This encapsulation has important implications:

  • Elements inside your widget are not accessible from the main document
  • document.querySelector() cannot reach your widget elements
  • document.currentScript may be null in the widget script context
  • Multiple widget instances each have their own Shadow DOM

Shadow DOM and Host Element

The host element is a <gs-cc-registry-widget> custom element that wraps your widget. To access elements inside your widget, query for the host element and access its shadowRoot.

Incorrect — does not work inside a widget:

js
// This returns null — document cannot see into Shadow DOM
const el = document.querySelector('.my-class');

Correct — query through the shadow root:

js
const hosts = document.querySelectorAll(
  'gs-cc-registry-widget[data-widget-type*="your_widget_type"]'
);
hosts.forEach(function(host) {
  const root = host.shadowRoot;
  if (root) {
    const el = root.querySelector('.my-class');
  }
});

Replace your_widget_type with the type value from your widget's extensions_registry.json entry.

The *= operator matches widgets whose data-widget-type attribute contains your type string — this is more robust than exact match (=) if the platform adds a prefix or suffix to the attribute value.

Script Context

document.currentScript may be null in the widget execution context. This affects patterns like:

js
// This may fail — document.currentScript can be null in the widget context
window.WIDGET_BASE_URL = document.currentScript.src.replace(/[^/]+$/, '');

Do not rely on document.currentScript inside widget code. Use the host element approach instead to interact with your widget's DOM.

Multiple Widget Instances

The same widget type can appear multiple times on a page. If you use querySelector (which returns only the first match), only one instance updates while the others remain unchanged.

Incorrect — only updates the first instance:

js
const host = document.querySelector('gs-cc-registry-widget[data-widget-type*="weather"]');

Correct — updates all instances:

js
const hosts = document.querySelectorAll('gs-cc-registry-widget[data-widget-type*="weather"]');
hosts.forEach(function(host) {
  const root = host.shadowRoot;
  if (root) {
    // Update this instance's DOM
  }
});

DOM Hierarchy

Full Example

A minimal widget that fetches weather data through a connector and updates its own DOM, combining the patterns above:

html
<!-- index.html — your widget entry file -->
<script src="https://static.customer-hub.northpass.com/widget-sdk/latest/index.umd.js"></script>

<div class="weather-widget">
  <p class="status">Loading...</p>
  <div class="result" style="display:none">
    <h3 class="city-name"></h3>
    <p class="temperature"></p>
  </div>
</div>

<script>
(async function() {
  // Find ALL instances of this widget type on the page
  const hosts = document.querySelectorAll(
    'gs-cc-registry-widget[data-widget-type*="weather"]'
  );

  for (var i = 0; i < hosts.length; i++) {
    var host = hosts[i];
    var root = host.shadowRoot;
    if (!root) continue;

    // Query elements through the shadow root, not document
    var statusEl = root.querySelector('.status');
    var resultEl = root.querySelector('.result');
    var cityEl   = root.querySelector('.city-name');
    var tempEl   = root.querySelector('.temperature');

    try {
      var sdk = new window.WidgetServiceSDK();
      var data = await sdk.connectors.execute({
        permalink: 'weather-api',
        method: 'GET',
        queryParams: { q: 'Warsaw' }
      });

      cityEl.textContent = data.city;
      tempEl.textContent = data.temperature + '°C';
      statusEl.style.display = 'none';
      resultEl.style.display  = 'block';
    } catch (err) {
      statusEl.textContent = 'Failed to load weather data.';
      console.error('Connector error:', err);
    }
  }
})();
</script>

Key points in this example:

  1. The SDK is loaded explicitly with a <script> tag — it is not auto-injected
  2. All host elements are iterated with a for loop to safely await each instance
  3. Elements are queried through host.shadowRoot, not document
  4. Each instance is updated independently in the loop

Next Steps

Gainsight CC Developer Portal