Appearance
Rendering & DOM
To read or update your widget's own markup, query through its Shadow DOM host rather than the main document — plain document.querySelector() calls cannot reach elements inside your widget. Use this guide when your widget needs to manipulate its rendered DOM directly, especially if multiple instances of the same widget type can appear 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 elementsdocument.currentScriptisnullin widget scripts- 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');1
2
2
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');
}
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
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.
Whichever selector you query with — root.querySelector(...) here, or sdk.$() / sdk.$$() inside init(sdk) — the selector must match an element that actually exists in your widget's own HTML. A query for a class or ID your markup never defines returns null (or an empty array from $$) rather than throwing — a typo'd or stale selector fails silently.
Script Context
document.currentScript is always null in widget scripts, so patterns that read document.currentScript.src fail. Use sdk.$() / sdk.$$() inside init(sdk) to interact with your widget's DOM, and see Hosting Widgets for resolving asset URLs.
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"]');1
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
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
DOM Hierarchy
Full Example
A minimal widget that fetches weather data through a connector and updates its own DOM, combining the patterns above:
Inside init(sdk), sdk.$() already queries your widget's own shadow root, and connectors from @gainsight-hub/sdk calls the connector (see SDK). See init(sdk) runtime reference for the init(sdk) object and Connectors SDK for connectors.
html
<!-- index.html — your widget entry file -->
<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 type="module">
import { connectors } from "@gainsight-hub/sdk";
export async function init(sdk) {
await sdk.whenReady();
const statusEl = sdk.$(".status");
const resultEl = sdk.$(".result");
try {
const data = await connectors.execute({
permalink: "weather-api",
method: "GET",
queryParams: { q: "Warsaw" },
});
sdk.$(".city-name").textContent = data.city;
sdk.$(".temperature").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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Key points in this example:
- The inline script is a module (
type="module"), so it can importconnectorsfrom@gainsight-hub/sdk— no script tag for the SDK is required - Customer Community calls the exported
init(sdk)once per widget instance, so multiple instances need no loop sdk.$()queries this instance's own shadow root, notdocument
Next Steps
- Widget Runtime — The
init(sdk)contract and SDK API reference - Widget Definition Reference — The
typefield used in querySelector selectors - Connectors SDK — Reference for
connectors - Card Grid widget in the template repository — Working example of Shadow DOM manipulation with dynamic content and connector data

