> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connectly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Install

> Install the widget with a paste-in script tag or the npm package, including React, Vue, Svelte, Solid, Angular and server-rendered frameworks 🧩

<Accordion title="Connectly Webchat is in development">
  This widget is being rolled out. Interfaces on this page may change before general
  availability. The engineering documentation site, which tracks the current build, is at
  [webchat.connectly.ai/docs](https://webchat.connectly.ai/docs/).
</Accordion>

Two paths, decided by a single question: does your site have an `npm install` step?

* **No build step** — a CMS, a page builder, a static site, a tag manager, anywhere you can
  paste HTML — use the **script tag**.
* **Bundling your own frontend** — React, Vue, Svelte, Solid, Angular — use the **npm
  package**. It ships ES modules and a typed React wrapper.

## The script tag

Paste this before the closing `</body>` tag. `<head>` also works — the script defers
itself until the page is ready either way.

```html theme={null}
<script defer src="https://webchat.connectly.ai/webchat2026.min.js"></script>
<connectly-webchat client-key="<your-client-key>"></connectly-webchat>
```

That is the whole integration. The launcher appears in the corner once the widget loads.

**Place the element; do not call a function.** `<connectly-webchat>` is a custom element,
and the browser upgrades whatever elements are already on the page the moment the definition
lands. Your markup may therefore sit before or after the script tag with the same result,
and this form works where a CMS, a theme or a tag manager lets you add markup but not run a
script.

If you do need the JS API, keep the call in a **second** tag carrying `defer` or
`type="module"`, after the first. Both attributes hold execution until the document has
parsed and then run in document order, so `window.ConnectlyWebchat` exists by the time the
second tag runs; a plain inline `<script>` with neither attribute runs first, against a
global that is not there yet. The sturdier form — and the one to use when you cannot control
tag order — is the ready event, which you may subscribe to before the widget exists because
it bubbles to `document`:

```html theme={null}
<script defer src="https://webchat.connectly.ai/webchat2026.min.js"></script>
<connectly-webchat client-key="<your-client-key>"></connectly-webchat>
<script>
  // A plain inline script is fine HERE: the listener is registered before the deferred
  // bundle runs, which is exactly the ordering problem the ready event solves.
  document.addEventListener('connectly-webchat:ready', (event) => {
    console.log('webchat ready', event.detail);
  });
</script>
```

`open()` is deliberately not what this example calls: it would pop the panel up for every
visitor on every page. Use the event to hook up analytics, and leave opening to the reader.

The event fires when the launcher renders, and is not replayed — register the listener
before the widget loads rather than after. It fires again if the widget is torn down and
re-initialized (a changed `client-key`, or an SPA remount), so it is once per launcher
rather than once per page.

## The npm package

```sh theme={null}
npm i git+https://github.com/connectlyai/webchat.git#v1.1.0
```

A git URL rather than a registry name: the package is distributed from its own GitHub
repository, which npm, pnpm and yarn all install from natively.

**Always pin a tag.** A git specifier resolves to a commit, so omit the fragment after `#`
and you get whatever is on the default branch at install time, which moves under you.
`#semver:^1.1.0` also works, resolving against the repository's tags. If the install asks
for credentials, see "npm cannot install the git URL" under Troubleshooting.

**ESM only, browser only.** There is no CommonJS build, so reach it through `import` or a
bundler, never `require()`.

## React

```tsx theme={null}
'use client'; // Next.js App Router only — see "Server rendering" below.

import { ConnectlyWebchat } from '@connectly/webchat/react';

export function App() {
  return <ConnectlyWebchat clientKey="<your-client-key>" />;
}
```

The component renders `null`. The widget is created imperatively and lives in `<body>`,
outside your tree; unmounting the component ends the session. `react` and `react-dom` are
optional peer dependencies, needed only for this entry point.

## Any other framework, or none

```js theme={null}
import '@connectly/webchat';
```

That import *is* the integration: it defines the `<connectly-webchat>` custom element and
configures the origin. Then put the element on the page.

```html theme={null}
<connectly-webchat client-key="<your-client-key>"></connectly-webchat>
```

Svelte, Vue, Solid and Angular all render custom elements natively, so the import plus the
tag is everything — with two exceptions that break the build otherwise.

**Vue** needs `isCustomElement`, or the compiler treats the tag as a component it cannot
resolve, warns on every render, and emits nothing:

```js theme={null}
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === 'connectly-webchat' } } })
```

**Angular** needs `schemas: [CUSTOM_ELEMENTS_SCHEMA]` on the component, or the template
fails with NG0304.

Where you put the element in the DOM does not matter — it is a 0×0, `pointer-events: none`
mount point, and the launcher is fixed to the viewport corner. One exception: an ancestor
with a `transform`, `filter` or `contain` becomes the containing block for
`position: fixed`, so the element relocates itself to `<body>` — which breaks any framework
that thinks it still owns that node. Append to `<body>` yourself, or use the React wrapper.

## Server rendering

**Never import the package on the server.** It defines a custom element extending
`HTMLElement`, which does not exist in a server runtime, so the *import itself* throws
`ReferenceError: HTMLElement is not defined`. That includes `@connectly/webchat/react`,
which imports the main entry.

Next.js, both routers — load it through `next/dynamic` with `ssr: false`, because even a
client component is prerendered during `next build`:

```tsx theme={null}
'use client'; // App Router only; also what allows `ssr: false` here.

import dynamic from 'next/dynamic';

const ConnectlyWebchat = dynamic(
  () => import('@connectly/webchat/react').then((m) => m.ConnectlyWebchat),
  { ssr: false },
);

export function Webchat() {
  return <ConnectlyWebchat clientKey="<your-client-key>" />;
}
```

Everywhere else — Remix, Nuxt, Astro, SvelteKit — the rule is the same: reach it from a
browser-only path (a dynamic `import()` in a mount or effect hook, a `<ClientOnly>`
wrapper, `client:only`, a plain module `<script>`), never a static import at the top of a
shared module.

## One integration per page

**Do not load the script tag and the npm package on the same page.**

You get two launchers, two sessions and two sockets for one visitor, and the transcript
splits unpredictably between them. Nothing errors and nothing warns — multiple widgets on
one page is deliberately supported for other cases — so this is a bug you will only find
by looking at it.
