Documentation

Developers

Widget JavaScript API

Install the Sela chat widget with one script tag, configure it with data attributes, and control it from your page with window.SelaWidget.

Last updated: 2026-09-29

The Sela widget is a small script (widget.js) that adds a launcher button to your page and opens the chat in an iframe hosted by Sela. This page covers the install attributes, the window.SelaWidget API, the analytics hook, framework examples and what your Content Security Policy must allow. If you are a business owner setting up the widget from the dashboard, read Web chat widget first.

Install with a script tag#

Add this snippet to every page where the widget should appear. Replace org_XXXXXXXX with your organization ID (shown in the dashboard under Integrations, in the Website Widget section).

index.html
<script
  src="https://usesela.com/widget.js"
  data-organization-id="org_XXXXXXXX"
  data-position="bottom-right"
  data-theme="auto"
  data-primary-color="#0f3fcc"
  data-button-size="medium"
  data-widget-icon="default"
  data-z-index="999999"
  data-hide-on-mobile="false"
  async
></script>

The script initializes itself when it loads. There is no separate init call. Both async and a normal blocking script work, and it can sit in <head> or before </body>.

The script must be a classic <script> tag with the attributes on the tag itself. It reads its configuration from document.currentScript, so a type="module" script or a script injected without the attributes will not configure correctly.

Script attributes#

AttributeValuesDefaultNotes
data-organization-idYour organization IDRequiredMust match ^[a-zA-Z0-9][a-zA-Z0-9_-]{1,127}$. A missing or invalid value disables the widget silently.
data-positionbottom-right, bottom-left, top-right, top-leftbottom-rightThe launcher sits 20 px from the edges. The panel opens 90 px from the edge.
data-themelight, dark, autoautoauto follows the visitor's prefers-color-scheme and updates live.
data-primary-color#rgb or #rrggbb#3b82f6Colors the launcher and is passed to the chat panel for accents. Any other format silently falls back to the default.
data-button-sizesmall (50 px), medium (60 px), large (70 px)medium
data-widget-icondefault, headset, robot, sparkles, help-circledefault
data-z-indexInteger from 1 to 2147483647999999The chat panel uses this value minus 1.
data-hide-on-mobiletrueOffChecked once when the widget renders: hides it when the window is 768 px wide or narrower.

Invalid values for the enumerated attributes revert to the default without an error.

Identity attributes such as data-email or data-user-hash do not exist. Identity verification does not work through the script tag; see Identity verification (HMAC).

What the script creates#

  • A launcher <button id="Sela-widget-button">.
  • A chat panel <div id="Sela-widget-container" role="dialog">, 400 by 600 px on desktop (capped at the viewport size), and nearly full width on phones.
  • An iframe pointing at https://widget.usesela.com/ with orgId, theme and primaryColor as query parameters. The iframe is allowed to use the microphone and clipboard.

The iframe loads lazily. Its src is set the first time the panel opens, or when the visitor hovers or focuses the launcher. The script also adds preconnect and dns-prefetch hints for the widget origin so the first open feels fast.

The window.SelaWidget API#

The script exposes one global, window.SelaWidget. There is no window.Sela alias; code that calls window.Sela fails.

MethodWhat it does
show()Opens the chat panel.
hide()Closes the chat panel.
isOpen()Returns true while the panel is open.
updateConfig(partial)Merges and sanitizes the new options. Only primaryColor, widgetIcon and theme change the page live.
init(partial)Destroys the widget and rebuilds it with the merged options. Use this for a full refresh.
destroy()Removes the launcher and panel and stops listening for messages.
getConfig()Returns a copy of the current config.

updateConfig and init take camelCase keys:

KeyValues
orgIdOrganization ID
positionbottom-right, bottom-left, top-right, top-left
themelight, dark, auto
primaryColor#rgb or #rrggbb
buttonSizesmall, medium, large
widgetIcondefault, headset, robot, sparkles, help-circle
zIndexInteger, 1 to 2147483647
hideOnMobileBoolean
controls.js
// Open the chat from your own "Contact us" button
document.querySelector("#contact-us").addEventListener("click", () => {
  window.SelaWidget?.show();
});

// Switch the icon and color live
window.SelaWidget?.updateConfig({ widgetIcon: "headset", primaryColor: "#0f3fcc" });

// Move the launcher: position changes need a rebuild
window.SelaWidget?.init({ position: "bottom-left" });

console.log(window.SelaWidget?.getConfig());
// { orgId, position, theme, primaryColor, buttonSize, widgetIcon, zIndex, hideOnMobile }

Timing rules#

  • show() and hide() do nothing until the widget has rendered, which happens after DOMContentLoaded. Guard calls with window.SelaWidget?. and, if you call very early, wait for DOMContentLoaded.
  • With data-hide-on-mobile="true" on a narrow screen nothing is ever created, so show() is a no-op there.

Events and analytics#

The widget does not fire DOM events and does not postMessage to your page. The one host-facing hook is a global analytics object. If window.selaAnalytics.track is a function, the script calls it as track(eventName, props). You can define it before or after the script loads.

analytics.html
<script>
  window.selaAnalytics = {
    track(name, props) {
      // Forward to your analytics tool
      console.log(name, props);
    },
  };
</script>
EventFired whenExtra properties
widget_initializedThe launcher is renderedposition, theme
widget_loadedThe chat iframe finished loadingNone
widget_errorThe chat iframe failed to loaderror: "iframe_load_failed"
widget_openedThe panel openedNone
widget_closedThe panel closedNone

Every call includes orgId and an ISO 8601 timestamp in props.

Framework examples#

Plain HTML#

Use the snippet at the top of this page.

React (Vite or Create React App)#

SelaWidget.tsx
import { useEffect } from "react";

export function SelaWidget({ organizationId }: { organizationId: string }) {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://usesela.com/widget.js";
    script.async = true;
    script.setAttribute("data-organization-id", organizationId);
    script.setAttribute("data-primary-color", "#0f3fcc");
    document.body.appendChild(script);
    return () => {
      window.SelaWidget?.destroy();
      script.remove();
    };
  }, [organizationId]);
  return null;
}

Mount it once near the root of your app. The cleanup removes the widget when the component unmounts, which avoids a duplicate launcher in React Strict Mode.

Next.js (App Router)#

app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://usesela.com/widget.js"
          strategy="afterInteractive"
          data-organization-id={process.env.NEXT_PUBLIC_SELA_ORG_ID}
          data-primary-color="#0f3fcc"
        />
      </body>
    </html>
  );
}

next/script renders the data-* attributes onto the script element, which is what the widget reads.

TypeScript typing#

sela.d.ts
interface Window {
  SelaWidget?: {
    init(config?: Record<string, unknown>): void;
    show(): void;
    hide(): void;
    destroy(): void;
    updateConfig(config: Record<string, unknown>): void;
    getConfig(): Record<string, unknown>;
    isOpen(): boolean;
  };
  selaAnalytics?: { track(name: string, props?: Record<string, unknown>): void };
}

Content Security Policy and host page notes#

If your site sends a Content Security Policy, allow Sela's script and iframe:

text
script-src  https://usesela.com;
frame-src   https://widget.usesela.com;

The widget iframe talks to Sela's backend itself, so your connect-src does not need to change. Other notes:

  • The chat runs in a cross-origin iframe, so your page's CSS and JavaScript cannot read or style its content. This keeps visitor conversations isolated from the host page.
  • Voice uses the microphone. If you set a Permissions-Policy header, do not block microphone for https://widget.usesela.com.
  • Single-page apps: add the script once. Route changes do not need a re-init.
  • Ad blockers or privacy extensions that block usesela.com also block the widget. Nothing appears in the console in that case, so test in a clean browser profile.
  • To hide the launcher on some pages, only include the script on the pages you want, or call window.SelaWidget.destroy().

Logged-in customers#

Because the script tag cannot carry a signed identity, recognizing logged-in users means loading the hosted widget page in your own iframe or WebView with a server-signed URL. The full recipe, with Node, Python and PHP samples, is in Identity verification (HMAC).

Troubleshooting#

SymptomLikely cause
Nothing appears and there are no errorsThe attribute is data-org-id instead of data-organization-id, the ID is invalid, or the script is type="module".
window.SelaWidget is undefinedThe script has not finished loading, was blocked by CSP or an ad blocker, or exited because of a bad organization ID.
Panel opens but stays blankframe-src (or default-src) in your Content Security Policy blocks https://widget.usesela.com.
Launcher hidden on phonesdata-hide-on-mobile="true" is set.
Position or size does not changeUse init() instead of updateConfig() for those keys.
Chat panel shows an organization errorThe organization ID does not match a Sela workspace. Copy it again from the dashboard.
Widget JavaScript API | Sela docs | Sela