Embed Chat to Website

Open in CMS

Put your Qlar agent on your own website so visitors can talk to it without leaving the page. The chat can sit inside your page (inline) or open from a button pinned to a corner of the window (floating). It works in a plain HTML page and in a React app, with no build step and no backend changes.


How it works

The chat is a standard Web Component called <qlar-component>. You load one script from Qlar's CDN, then place the element where the chat should appear. HTML attributes on the element control how it looks and behaves.

<script type="module" crossorigin src="https://app-container.qlar.ai/qlar-component.mjs"></script>

When the element renders, it connects to your agent and runs the whole conversation UI.

Already using <pusaka-container>? That is the element's earlier name. The script still registers it, so existing pages keep working. Use <qlar-component> for new embeds.


Before you start

  • You need a Qlar account with an agent that is published. Your live site always talks to the published agent.
  • You need to be able to add a <script> tag to your page.
  • Opening-line settings (see Opening lines from websites) reach your live site only after you save them and publish the agent.

Open the Embed page

In the Qlar CMS, click the hamburger menu (☰) in the top-left corner, then open Channels β†’ Embed. Everything on the page applies to the active agent.

The page has three blocks, top to bottom:

BlockWhat it is for
Build your embedThe fastest path. Open builder opens the builder in a new browser tab. Copy basic snippet copies a small working embed.
Opening lines from websitesWhat an embedding site may send to open the chat in place of the Welcome Message. This is the only saved setting on the page.
Developer guideThe hand-wiring guide: Install, In React, Layout, Floating panel, Theme and colors, Top bar, Behavior, Opening line from code, and Attribute reference. It is a short version of this page.

Older links such as /widget?tab=introduction still work. They scroll to the matching block.


Build it in the builder

Click Open builder on the Build your embed card. The builder opens in its own browser tab, without the CMS around it, because what you look at there is a sample website with your chat on it. That is also the only way to see floating mode as your visitors will: pinned to a corner of a real page.

On a wide screen, the settings are on the left and the sample page is on the right. On a phone or a narrow window, the page stays on screen and the settings and code open in a sheet from a bottom navigation bar. You can drag the sheet between half and full height.

Start from a preset

The Layout tab opens with a preset strip. Pick Change to see the three starting points:

PresetForWhat it sets
Full appA chat page of its ownFills the whole page.
Page sectionPart of an existing pageChat only, no top bar, no examples.
Support bubbleA launcher on every pageFloating, chat only, no top bar, no logo, panel title "Need help?".

A preset sets only what it is about and puts every other field back to its default. Once you edit a field, the preset shows Modified. After you apply a preset or use Reset settings, an Undo button stays on screen for a few seconds.

The settings tabs

TabWhat you set
LayoutHow the chat sits on the page: Inline or Floating. For floating: Position (Corner, Horizontal, Vertical) and Panel size (Width, Height). For inline: Size, the height of the box on the sample page. That height is not an attribute; 100% makes the chat the whole page.
LookTheme (System, Light, Dark), What the chat shows (Full app or Chat only), Show the agent logo above the greeting. For floating: Show the panel header, Panel title (blank means "Chat") and Panel header colors (Light bar, Light text, Dark bar, Dark text).
Top barShow the top bar, and the single items: conversation list (menu), new chat, light/dark toggle, sign-in and account menu.
IntroShow example messages under the greeting, and What your site sends: Nothing, Introduction text, Introduction prompt, or Introduction prompt, no system prompt. Fill in a sample writes an example for you.
BehaviorFocus the chat input on load, and Resume window for anonymous visitors: 30 min, 1 hour, 6 hours (the default), 1 day, 7 days, or Custom… in minutes, hours or days.

A dot on a tab means it holds changes. Turn on Show attribute names at the bottom of the settings to see the data-* attribute next to each field. Fields that do nothing in the layout you picked are hidden, not greyed out.

Chat only also turns the top bar off. In the builder, picking Chat only switches Show the top bar off, and picking Full app switches it back on. In your own code, the top bar is controlled by data-show-top-bar alone. See Top bar.

Preview

Press Start to run your settings as a real page with a live chat. After you change something, the preview says "Settings changed. Restart to see them." and Start becomes Restart.

The bar above the page has:

  • Draft: the preview runs your draft agent, meaning your latest save, even if it is not published yet.
  • Anonymous: the preview chats as a visitor who has not signed in.
  • A width picker: Desktop, Tablet (768px) and Phone (390px). Below 480px a floating panel fills the screen, so the phone width shows that.
  • Live update (Live on narrow screens): rebuilds the preview shortly after you stop changing settings. Each rebuild starts a new chat. With an introduction prompt, each rebuild also costs a model call.

Get the code

  • Show code opens The element for your page, with three views: Element (just the tag), HTML page and React. Only settings that differ from the defaults are included, so change two things and you get a two-attribute element.
  • Download gives you HTML page (qlar-embed.html, opens in any browser) or React component (QlarChat.tsx, the element with its TypeScript declaration and a comment that tells you to load the script in index.html).
  • More actions has Copy settings link, which copies a link that opens the builder with the same settings, and Reset settings, which puts every field back to its default.

Draft preview, published code. The preview runs your draft agent, so changes you saved but have not published show up there. The code and both downloads use your published agent, which is the one your live site should use.


Or copy the basic snippet

Copy basic snippet on the Build your embed card copies the script and an inline chat in a 600px-high box, with your agent ID already filled in:

<script type="module" crossorigin src="https://app-container.qlar.ai/qlar-component.mjs"></script>

<div style="height: 600px">
  <qlar-component
    data-agent-id="YOUR_AGENT_ID"
  ></qlar-component>
</div>

Paste it into your page's HTML. Keep the wrapper <div> or give the chat's parent a height some other way. An inline chat takes the size of its parent, and a parent with no height means a chat with no height.


Add it by hand

1. Load the script

Add the script to your page, usually inside <head>. It registers the <qlar-component> element.

<script type="module" crossorigin src="https://app-container.qlar.ai/qlar-component.mjs"></script>

Load it once per page. You can then place as many <qlar-component> elements as you need.

2. Place the element

Replace YOUR_AGENT_ID with your agent ID. You find it in the Developer guide on the Embed page: in the Install snippet and in the Identity row of the Attribute reference. Copy basic snippet also includes it. It looks like a UUID, for example a1b2c3d4-e5f6-7890-abcd-ef1234567890.

A complete page looks like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My Website</title>
    <script type="module" crossorigin src="https://app-container.qlar.ai/qlar-component.mjs"></script>
  </head>
  <body>
    <!-- Give the parent a height so the chat has room to render -->
    <div style="width: 400px; height: 600px;">
      <qlar-component
        data-agent-id="YOUR_AGENT_ID"
        data-theme="system"
      ></qlar-component>
    </div>
  </body>
</html>

Open the page in a browser. The chat appears inside the <div>.


Use it in React

React does not know custom elements by default, so you load the script in index.html and declare the element for TypeScript once.

1. Load the script in index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>React App</title>
    <script type="module" crossorigin src="https://app-container.qlar.ai/qlar-component.mjs"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

2. Declare the element

Edit src/vite-env.d.ts or create src/custom-elements.d.ts:

/// <reference types="vite/client" />
import React from "react";

declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "qlar-component": React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      > & {
        "data-agent-id"?: string;
        "data-theme"?: string;
        "data-app-mode"?: string;
        "data-enable-autofocus"?: string;
        "data-thread-ttl"?: string;
        "data-mode"?: string;
        "data-position"?: string;
        "data-offset-x"?: string;
        "data-offset-y"?: string;
        "data-chat-width"?: string;
        "data-chat-height"?: string;
        "data-chat-title"?: string;
        "data-show-title"?: string;
        "data-show-agent-logo"?: string;
        "data-intro-message"?: string;
        "data-intro-prompt"?: string;
        "data-intro-raw-prompt"?: string;
        "data-show-examples"?: string;
        "data-wa-handoff"?: string;
        "data-show-top-bar"?: string;
        "data-show-menu"?: string;
        "data-show-new-chat"?: string;
        "data-show-theme-toggle"?: string;
        "data-show-sign-in"?: string;
        "data-light-title-bg"?: string;
        "data-light-title-fg"?: string;
        "data-dark-title-bg"?: string;
        "data-dark-title-fg"?: string;
      };
    }
  }
}

export {};

3. Render the element

The simplest React embed is floating mode. The element opens and closes its own panel, so it needs no wrapper component and no state. Place it anywhere in your tree:

export default function App() {
  return (
    <>
      {/* Your app content */}

      <qlar-component
        data-agent-id="YOUR_AGENT_ID"
        data-mode="floating"
        data-position="bottom-right"
        data-chat-title="Your Agent Name"
      ></qlar-component>
    </>
  );
}

For an inline chat, give the parent a height:

export default function Chat() {
  return (
    <div style={{ height: 600 }}>
      <qlar-component data-agent-id="YOUR_AGENT_ID"></qlar-component>
    </div>
  );
}

Want your own launcher button or panel? Put an inline <qlar-component> inside your own container and show or hide that container yourself. Floating mode is not needed for this.


Layout: inline or floating

By default the chat is inline: it fills the element it sits in, so the parent decides its size. Give the parent a height, or the chat has none.

<div style="height: 600px">
  <qlar-component data-agent-id="YOUR_AGENT_ID"></qlar-component>
</div>

data-mode="floating" switches to a launcher button pinned to a corner of the window. The button opens and closes a chat panel and stays visible while the visitor scrolls. The element takes no space in the page flow, so you can put it anywhere in <body> without a wrapper.

<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-mode="floating"
  data-position="bottom-right"
  data-chat-title="Your Agent Name"
></qlar-component>

Floating panel

  • data-position picks the corner: bottom-right, bottom-left, top-right or top-left.
  • data-offset-x and data-offset-y set the button's distance from the window edge.
  • data-chat-width and data-chat-height set the panel size.
<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-mode="floating"
  data-position="bottom-right"
  data-offset-x="24px"
  data-offset-y="24px"
  data-chat-width="400px"
  data-chat-height="600px"
  data-chat-title="Your Agent Name"
></qlar-component>

Tip: On screens narrower than 480px, the floating panel fills the whole screen whatever its width and height are. It works on phones with no extra setup.


Theme and colors

  • data-theme accepts system (the default, follows the visitor's device), light or dark.
  • data-show-agent-logo="false" hides the agent logo above the greeting.
  • data-chat-title sets the floating panel's title (default "Chat"). data-show-title="false" hides the panel's header bar (title, expand and close buttons); the launcher button stays visible to close the panel, and the panel moves clear of it. On screens narrower than 480px, where the panel fills the screen, the header bar still shows so visitors can close the panel.
  • data-app-mode accepts full-app or chat. Both look the same today. It does not hide the top bar. Use data-show-top-bar for that.

The floating panel's header bar uses your agent's theme color, about 18% darker, unless you set its colors. Light and dark mode each take their own pair, so the panel can match your site in both:

<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-mode="floating"
  data-light-title-bg="#5651D4"
  data-light-title-fg="#FFFFFF"
  data-dark-title-bg="#2A2850"
  data-dark-title-fg="#FFFFFF"
></qlar-component>

Top bar

The top bar holds the conversation list (menu), new chat, the light/dark toggle and sign-in. data-show-top-bar="false" hides the whole bar. This is the only way to hide it.

To keep the bar and hide single items, leave it on and turn off the items you don't want:

<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-show-menu="false"
  data-show-theme-toggle="false"
  data-show-sign-in="false"
></qlar-component>

The per-item attributes apply only while data-show-top-bar is true.


Behavior

Autofocus. By default the message box takes focus when the chat loads and after each answer. If the chat sits partway down a long page, that focus jump looks like the page scrolling on its own. Set data-enable-autofocus="false" to stop it.

Resume window. In floating mode, an anonymous visitor who reloads the page or moves to another page with the chat picks up the same conversation. data-thread-ttl sets how long that works, in milliseconds, counted from the last message. The default is 21600000 (6 hours). After that, a new conversation starts.

<!-- Resume for 1 day instead of 6 hours -->
<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-mode="floating"
  data-thread-ttl="86400000"
></qlar-component>
  • It works in floating mode only. An inline chat starts a new conversation on every page load.
  • It applies to anonymous visitors only.
  • It lives in the visitor's browser. Nothing is deleted on the server when it runs out.

If your agent's WhatsApp channel uses Redirect to web (bridge mode), every WhatsApp message is answered with a one-time sign-in link instead of a reply in WhatsApp. The link can point to a page on your own site that embeds the chat. See WhatsApp β†’ Redirect to web for the channel settings.

The link looks like this. Qlar adds the last two parameters to the target URL you set, keeping any query it already has:

https://www.example.com/help?wa_code=K7PQ2XMA&wa_agent=YOUR_AGENT_ID

Nothing needs to change on your page:

  • Picked up automatically. The chat reads wa_code from the page address, signs the visitor in as their WhatsApp account, and removes wa_code and wa_agent from the address bar. Other parameters stay.
  • The floating panel opens by itself, so the visitor lands in the conversation instead of looking for the launcher.
  • What they wrote on WhatsApp is sent as the first message as soon as the chat is ready, so the agent answers it right away.
  • Several chats on one page. wa_agent is the agent's published ID, the same value as data-agent-id. Only the chat for that agent takes the code.
  • The guest conversation is not carried over. A floating chat does not resume the visitor's earlier anonymous conversation, so it is never attached to their WhatsApp account.
  • Expired or used links show "This link is no longer valid" with a way to continue. The visitor sends another WhatsApp message to get a new link.

To keep a chat out of this, set data-wa-handoff="false". That chat ignores the code and leaves the page address as it is, for example when your own page handles wa_code:

<qlar-component data-agent-id="YOUR_AGENT_ID" data-wa-handoff="false"></qlar-component>

Opening lines from websites

By default every visitor sees your agent's Welcome Message (set in Conversation β†’ Introduction). Your site can replace it per visitor, for example to greet someone by name or to open with a summary of the order they are looking at.

You may not need a route at all: the Welcome Message itself can greet signed-in visitors by name, greet by time of day, or change with conditions, using variables such as {{user.first_name}} and {{time.greeting}}. See Variables and Conditions. Text your site sends through a route is shown as sent: Qlar does not fill in {{ … }} in it, so your site can use its own templating.

Turn on a route

On Channels β†’ Embed, find the Opening lines from websites card. Each switch opens one route:

SwitchAttributeWhat it does
Custom textdata-intro-messageShown exactly as the site sends it. No model call.
Custom promptdata-intro-promptYour agent answers the prompt with its persona, tools and knowledge. Visitors see the answer, never the prompt.
Prompt without system prompt (marked Needs guardrail)data-intro-raw-promptAnswered with no system prompt at all, for turning your site's own data into an opening line, like an order summary. Your agent's persona and guardrails do not apply.

When you turn on Prompt without system prompt, a Guardrail for this route field appears. It is required. Say what the route is for and tell it to refuse anything else, for example:

You turn car-rental order data into one short opening line in Indonesian. Refuse any other request.

The guardrail is the only instruction the model gets on that route. Without it, the route would answer whatever a site sends, so a blank guardrail keeps the route off.

The chip next to the card title shows what is open for the saved agent: Welcome Message only, 1 route open, 2 routes open, and so on. It adds incl. no system prompt when the third route is on.

Save, then publish

Click Save, then publish the agent with Publish. Your live site uses the published agent, so a saved route does nothing there until you publish.

Be careful with the third switch. It skips your agent's persona and its guardrails: the model gets your site's text and nothing else. Turn it on only for sites you control. Use Custom prompt when the greeting should sound like your agent.

How the routes behave

  • One wins. If a page sets more than one attribute, text wins over prompt, and prompt wins over prompt without system prompt.
  • Off means ignored. With a route's switch off, its attribute is ignored and the visitor sees the Welcome Message. Nothing reports an error.
  • Billing. The two prompt routes make one model call per conversation, billed as AI usage. Generated opening lines are also capped per agent per hour, so a flood of requests cannot run up an unbounded bill. Past the cap, visitors see the Welcome Message instead.
  • Privacy. The attributes go to the chat privately, not through the page URL. A prompt with customer data does not end up in the visitor's browser history or in server access logs.

Send it from your code

<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-intro-message="Welcome back, Sarah. Your order #4821 is out for delivery."
></qlar-component>

The attributes are read once, as the chat starts. Calling setAttribute on an element that is already running does nothing and shows no error. Set the values before the element reaches the page, or replace the element to start again.

Render it with the page. The simplest and safest option. Your server already knows who the visitor is, so it writes the attribute into the HTML it sends:

<!-- server-rendered, e.g. from a template -->
<qlar-component
  data-agent-id="YOUR_AGENT_ID"
  data-intro-prompt="Greet {{ customer.name }} and mention their open order {{ order.id }}."
></qlar-component>

Build the element after your data arrives. When the opening line depends on data you fetch in the browser, don't put the element in your HTML and fill it in later. Create it once the data is there, set the attributes, then add it to the page:

<div id="chat-slot" style="height: 600px"></div>

<script type="module">
  const order = await fetch("/api/orders/latest").then((r) => r.json());

  const chat = document.createElement("qlar-component");
  chat.setAttribute("data-agent-id", "YOUR_AGENT_ID");
  chat.setAttribute(
    "data-intro-prompt",
    `Greet the customer and summarise this order in one sentence: ${JSON.stringify(order)}`
  );

  // Attributes first, page second.
  document.getElementById("chat-slot").appendChild(chat);
</script>

Start again with a new opening line. To start a fresh conversation with a different opening line, for example when the visitor switches orders, replace the element instead of editing it:

function restartChat(slot, introPrompt) {
  slot.replaceChildren();

  const chat = document.createElement("qlar-component");
  chat.setAttribute("data-agent-id", "YOUR_AGENT_ID");
  chat.setAttribute("data-intro-prompt", introPrompt);
  slot.appendChild(chat);
}

In React, render nothing until the data is ready, and give the element a key from that data. React then replaces the element instead of updating it:

function OrderChat({ order }: { order?: Order }) {
  if (!order) return null;

  return (
    <div style={{ height: 600 }}>
      <qlar-component
        key={order.id}
        data-agent-id="YOUR_AGENT_ID"
        data-intro-prompt={`Greet the customer and summarise this order: ${order.summary}`}
      ></qlar-component>
    </div>
  );
}

Test it in the builder

Write and test an opening in the builder on the Opening lines from websites card opens the builder. If you have unsaved changes on the card, you are asked first, because the builder uses your saved routes.

In the builder, go to the Intro tab, choose a route under What your site sends, write the opening line (or use Fill in a sample) and press Start. The preview runs your draft agent, so a route you just saved works there before you publish.

Routes the agent does not allow are greyed out. Turn them on in Opening lines from websites, save, then click Check again in the builder. It reads the switches again without reloading, so you keep your settings.

The two prompt routes call the model each time the preview starts, and that is billed to the agent.


Attribute reference

Only data-agent-id is required. Leave an attribute out and its default applies.

Identity

AttributeDefaultDescription
data-agent-id(required)The agent the chat talks to.

Layout

AttributeDefaultDescription
data-modeinlineinline fills the parent element. floating renders a button fixed to a window corner that opens and closes a chat panel.
data-positionbottom-rightCorner for the floating button: bottom-right, bottom-left, top-right, top-left. Floating mode only.
data-offset-x20pxHorizontal distance between the button and the window edge. Any CSS length. Floating mode only.
data-offset-y20pxVertical distance between the button and the window edge. Any CSS length. Floating mode only.
data-chat-width380pxWidth of the floating panel. Any CSS length. Fills the screen below 480px window width. Floating mode only.
data-chat-height580pxHeight of the floating panel. Any CSS length. Floating mode only.

Appearance

AttributeDefaultDescription
data-themesystemsystem (follows the device), light or dark.
data-app-modefull-appfull-app or chat. Both look the same today. To hide the top bar, use data-show-top-bar.
data-show-agent-logotrueShow or hide the agent logo above the greeting.
data-chat-titleChatTitle in the floating panel's header bar. Floating mode only.
data-show-titletrueShow or hide the floating panel's header bar: title, expand and close buttons. When hidden, the launcher button closes the panel. Below 480px window width the header bar always shows. Floating mode only.

Header colors

AttributeDefaultDescription
data-light-title-bg(agent theme, ~18% darker)Header bar background in light mode, as a CSS hex value, e.g. #5651D4. Floating mode only.
data-light-title-fg(not set)Header bar title text and icon color in light mode, as a CSS hex value. Floating mode only.
data-dark-title-bg(agent theme, ~18% darker)Header bar background in dark mode. Floating mode only.
data-dark-title-fg(not set)Header bar title text and icon color in dark mode. Floating mode only.

Top bar

AttributeDefaultDescription
data-show-top-bartrueMaster switch for the top bar. false hides every item in it. The attributes below apply only while this is true.
data-show-menutrueShow or hide the hamburger menu (conversation list).
data-show-new-chattrueShow or hide the new-conversation button.
data-show-theme-toggletrueShow or hide the light/dark theme toggle.
data-show-sign-intrueShow or hide the sign-in button, or the account menu once signed in.

Opening line

AttributeDefaultDescription
data-intro-message(Welcome Message)Text shown exactly as given. Requires Custom text under Opening lines from websites.
data-intro-prompt(none)A prompt your agent answers. The answer becomes the opening line. Requires Custom prompt.
data-intro-raw-prompt(none)A prompt answered with no system prompt at all. Requires Prompt without system prompt, with a guardrail.
data-show-examplestrueShow or hide the example messages under the greeting (set in Conversation β†’ Introduction). Needs no switch, and does not affect the follow-up suggestions after each answer.

Behavior

AttributeDefaultDescription
data-enable-autofocustrueFocuses the chat input on load and after each answer. false turns it off and avoids an unexpected scroll to the chat.
data-thread-ttl21600000Anonymous visitors in floating mode only. How long, in milliseconds, a conversation can be resumed after the page reloads, counted from the last message. The default is 6 hours. Applies in the visitor's browser only.
data-wa-handofftruePicks up a WhatsApp sign-in code (wa_code) from the page address, signs the visitor in and opens a floating panel. false makes this chat ignore the code. See Sign-in links from WhatsApp.

Troubleshooting

The chat does not show, or is a thin strip. An inline chat takes its parent's height. Give the parent a height (for example height: 600px), or use data-mode="floating". Also check that the script loads once and that data-agent-id holds your agent ID.

The opening line is ignored and the Welcome Message shows. Check in this order:

  1. The route's switch is on under Opening lines from websites, and you clicked Save.
  2. You published the agent after saving. Live sites use the published agent.
  3. For Prompt without system prompt, the guardrail is filled in.
  4. The attribute was on the element before it reached the page. Setting it later does nothing.
  5. Only one attribute is set, or you expect the one that wins (text, then prompt, then prompt without system prompt).

The top bar still shows with data-app-mode="chat". That is expected. App mode does not hide the top bar. Add data-show-top-bar="false".

The conversation does not resume after a reload. Resume works only for anonymous visitors in floating mode, within the resume window (data-thread-ttl, 6 hours by default, counted from the last message). An inline chat always starts fresh.

The page jumps to the chat on load. Autofocus moves focus into the message box. Set data-enable-autofocus="false".

A WhatsApp link opens the page, but the visitor is not signed in. Check that wa_agent in the link equals the chat's data-agent-id (the published agent ID), and that the chat does not set data-wa-handoff="false". A link that was already used, or is older than 30 minutes, shows "This link is no longer valid".

The builder preview works, but the live site does not. The preview runs your draft agent. Your site uses the published agent. Publish the agent to apply your saved changes.


Next steps

  • Test your agent: use Simulation in the Qlar CMS to check your agent's answers before you go live.
  • Refine its personality: adjust tone and interaction style under Persona β†’ Behavior.
  • Set the Welcome Message: see Introduction.
  • Connect more channels: add your agent to WhatsApp or Instagram from Channels in the CMS sidebar.