Skip to main content

Widget Installation

The Appilot Widget is a Web Component that you embed in your web application. It provides contextual AI assistance to your users without them needing to install anything.

Current status

Script tag integration and the appilot NPM package (bootAppilotWidget) are both fully supported, including the federation handshake for identifying your users (see Connecting your users). Framework-specific wrappers are planned for a future release.

Prerequisites​

  • An Appilot organization account. Access is by invitation while the beta runs; request one at appilot.space.
  • Your domain registered in the Backoffice. This is what selects your organization on a live site.
  • A Widget API Key is optional on a registered domain (see the decision below). Generate one in the Backoffice under Widget Keys when you need it.

Do I need an API key? (read this first)​

The answer depends on where the widget runs, because Appilot needs exactly one thing from a request: which organization it belongs to.

Where the widget runsNeed a key?Why
A registered production domainNo (optional)Appilot resolves your organization from the page domain. A bare <script src=".../appilot.js"> is enough.
Your own machineDepends on the tierRegister a hostname that resolves to 127.0.0.1 and no key is needed. On bare localhost a wk_test_ key is required, and the turn gets no app context. See Develop on localhost.
Server-to-server / headless (no browser Origin)Yes (wk_live_)No domain is sent, so the key is the only tenant identifier.

About the Widget API Key​

In one paragraph, so there is no confusion:

The Widget API Key is a public identifier (like a Stripe publishable key), not a secret. When you use one, it ships in your page's HTML on purpose. It (1) tells Appilot which organization owns the widget in places where the domain cannot, mainly localhost and server-to-server, (2) is the bucket Appilot rate-limits and bills, and (3) can be revoked. On a registered production site your domain selects your organization, so the key is optional there. Either way, you do not need to hide it.

Every key comes with a paired server secret (wsk_secret_...), shown once at creation. The secret is the opposite of the key: it is private, lives only on your backend, and is what proves a user's identity to Appilot (see Connecting your users below). Public key in the browser, secret on the server.

You generate keys in the Backoffice:

  1. Go to the Widget Keys page
  2. Click Create Key
  3. Name the key (e.g., "Production" or "Staging")
  4. Add your allowed domains (e.g., app.example.com)
  5. Copy BOTH the key and the server secret (each shown once). The key starts with wk_live_ (production) or wk_test_ (development); the secret starts with wsk_secret_.
tip

A wk_test_ key is for your own machine. The cloud accepts one only while every domain on its allowed list is a loopback address (localhost, 127.0.0.1, [::1], or a *.localhost name), so a development key cannot quietly become a production one. A live key needs a verified domain: see Verify your domain. Lost the secret? Regenerate it on the key's page; the public key is unchanged.

Connecting your users​

Appilot ties every conversation to an identified user, so there is no anonymous usage to pay for unexpectedly. There is no second login for your users: your app stays the source of identity. This section is the summary; the exact HTTP contract, its error codes and examples in Python and Ruby are on The identity relay. How it works depends on your setup:

  • Your app has its own accounts (the common case). After your user logs in, your backend makes one signed call to exchange your widget key + server secret + the user's id for a short-lived Appilot token, then hands that token to the widget. Your users never see an "Appilot account".

    POST https://api.appilot.space/widget/token
    Headers: X-Widget-Key: wk_live_... X-Widget-Secret: wsk_secret_...
    Body: { "externalId": "your-app-user-id", "email": "user@example.com", "displayName": "Jane" }
    → { "token": "...", "expiresIn": 3600 }

    Pass the returned token to the widget as data-user-token (or userToken in the JS API):

    <script
    src="https://cdn.appilot.space/widget/v1/appilot.js"
    data-api-key="wk_live_..."
    data-user-token="<the token your backend just minted>"
    async
    ></script>

    The widget sends that token as Authorization: Bearer on every turn, so the conversation is attributed to your user. If your app authenticates after the page loads, render the tag without the token first and re-init once you have it.

    You do not have to write that exchange yourself. The appilot-server package is that endpoint, for Node 18+ and with no dependencies of its own:

    pnpm add appilot-server
    // app/api/widget/token/route.ts
    import { createWidgetTokenHandler } from 'appilot-server';

    export const POST = createWidgetTokenHandler({
    apiUrl: 'https://api.appilot.space',
    widgetKey: process.env.APPILOT_WIDGET_KEY!,
    widgetSecret: process.env.APPILOT_WIDGET_SECRET!,
    resolveUser: async request => {
    const session = await getSession(request); // your session, your rules
    if (!session) return null; // 401 IDENTITY_REQUIRED
    return { externalId: `acme:${session.userId}`, displayName: session.name };
    },
    });

    createWidgetTokenHandler follows the fetch standard, so it drops into Next.js, Remix, Hono, Cloudflare Workers, Deno and Bun. Express and Fastify use createNodeWidgetTokenHandler from appilot-server/node, kept in a separate entry point so a Workers build never pulls node:http into its bundle.

    resolveUser is the security boundary, and the reason to prefer the package over a hand-rolled fetch: the caller never says who they are, your session does. The package also keeps the secret out of every response and turns a parseable-but-wrong upstream reply into a typed error rather than a token of undefined.

  • Your users also use the Appilot extension. If some of your federated users also use the Appilot Chrome extension while roaming, they can link their Appilot account to their identity in your app, so both surfaces recognize the same person and share conversations and role. See Account Linkage.

  • You have no user accounts at all. Enable the widget's built-in login/register (data-user-auth="true") so users sign in with an Appilot account directly in the panel. See Identity & Sign-in for the trade-offs.

  • Enterprise SSO (OIDC / SAML). For corporate deployments, your organization can connect its identity provider so members sign in via corporate SSO. This is configured per organization (see Enterprise SSO); talk to us to set it up.

caution

The /widget/token call uses your server secret and MUST run on your backend, never in the browser. The public key alone cannot mint a user token.

Option 1: Script Tag (Simplest)​

The widget ships as a tiny ES module loader: the browser downloads only a small launcher first, and the full assistant is fetched the moment a user opens the panel. This keeps your page fast. Add a matching nomodule line so older browsers that do not support ES modules still load the single-file build.

On a registered domain, add this before your closing </body> tag, no key required:

<script type="module" src="https://cdn.appilot.space/widget/v1/appilot.esm.js"></script>
<script nomodule src="https://cdn.appilot.space/widget/v1/appilot.js"></script>

The widget automatically renders a floating button and is ready to use. Appilot resolves your organization from the page domain.

On localhost/dev (no registered domain) or server-to-server, add your key. Put the same data-* attributes on BOTH lines: only the line your browser supports runs, so duplicating them keeps the configuration intact either way.

<script type="module" src="https://cdn.appilot.space/widget/v1/appilot.esm.js"
data-api-key="wk_test_a1b2c3d4e5f6..."></script>
<script nomodule src="https://cdn.appilot.space/widget/v1/appilot.js"
data-api-key="wk_test_a1b2c3d4e5f6..."></script>

With additional options​

<script type="module" src="https://cdn.appilot.space/widget/v1/appilot.esm.js"
data-api-key="wk_live_a1b2c3d4e5f6..."
data-position="bottom-left"
data-theme="dark"
data-language="es"></script>
<script nomodule src="https://cdn.appilot.space/widget/v1/appilot.js"
data-api-key="wk_live_a1b2c3d4e5f6..."
data-position="bottom-left"
data-theme="dark"
data-language="es"></script>
Serving from a CDN

The ES module loader fetches its code chunks relative to its own URL, so host appilot.esm.js and its appilot-*.js chunks together in the same folder. Module scripts are always fetched with CORS, so a cross-origin CDN must send Access-Control-Allow-Origin. The single-file nomodule build has no separate chunks and no CORS requirement.

Already using the older single <script src=".../appilot.js" async> tag? It still works unchanged: it falls back to the single-file build. The two-line snippet above just adds the faster module path for modern browsers.

Option 2: JavaScript API (More Control)​

Load the bundle without any data-* configuration and mount it yourself. The bundle exposes initAppilot on window.Appilot, and calling it twice replaces the previous instance rather than adding a second one.

<script src="https://cdn.appilot.space/widget/v1/appilot.js" async></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
window.Appilot.initAppilot({
apiKey: 'wk_live_a1b2c3d4e5f6...',
theme: 'dark',
language: 'es',
position: 'bottom-left',
});
});
</script>

A bare loader tag with no configuration logs a debug line and waits for this call, so nothing is mounted twice. The full surface on window.Appilot is in the API Reference.

Option 3: NPM Package​

For applications built with bundlers (Webpack, Vite, etc.), install the SDK and boot the widget programmatically:

pnpm add appilot
import { bootAppilotWidget } from 'appilot';

bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.space/widget/v1/appilot.esm.js',
widgetKey: 'wk_live_a1b2c3d4e5f6...', // optional on a registered domain
tokenEndpoint: '/api/widget/token', // your backend relay that mints an identified token
theme: 'light',
position: 'bottom-right',
});

bootAppilotWidget loads the widget bundle, obtains an identified user token from your tokenEndpoint relay, and keeps it refreshed for the life of the page. Track readiness with getWidgetAvailability() / subscribeWidgetAvailability(listener) (see the API Reference).

Framework-specific examples​

Framework wrappers are planned

Dedicated React/Vue wrappers and a teardown call (destroyAppilot) are planned for a future release. Today you call bootAppilotWidget directly from your framework's mount lifecycle, as shown below. bootAppilotWidget is idempotent while the widget is healthy, so a re-render will not boot it twice.

React​

import { useEffect } from 'react';
import { bootAppilotWidget } from 'appilot';

function App() {
useEffect(() => {
bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.space/widget/v1/appilot.esm.js',
widgetKey: import.meta.env.VITE_APPILOT_WIDGET_KEY,
tokenEndpoint: '/api/widget/token',
});
}, []);

return <div>{/* Your app content */}</div>;
}

Vue​

<script setup>
import { onMounted } from 'vue';
import { bootAppilotWidget } from 'appilot';

onMounted(() => {
bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.space/widget/v1/appilot.esm.js',
widgetKey: import.meta.env.VITE_APPILOT_WIDGET_KEY,
tokenEndpoint: '/api/widget/token',
});
});
</script>

Option 4: Self-Hosted (On-Premise)​

For organizations that need full control over the infrastructure:

  1. Take appilot.js (or appilot.esm.js and its chunks) from the CDN at https://cdn.appilot.space/widget/v1/
  2. Host it on your internal server or CDN
  3. Point the widget to your own Appilot backend:
<script
src="/assets/appilot.js"
data-api-key="wk_live_a1b2c3d4e5f6..."
data-api-url="https://appilot.internal.company.com"
async
></script>

The data-api-url attribute tells the widget where to send requests. Without it the bundle uses the host it was built for, which for the CDN build is the Appilot cloud. What else an on-premise deployment involves is in On-premise.

How it works under the hood​

The widget uses Shadow DOM for isolation:

Your Page
├── Your HTML, CSS, JS (unaffected)
└── <appilot-assistant>
└── #shadow-root (closed)
├── Appilot styles (isolated)
├── Floating button
└── Side panel UI
  • CSS isolation: Appilot's styles don't leak into your page, and your styles don't affect Appilot
  • DOM access: The widget can read your page's DOM (forms, fields) for context
  • No iframes: Direct DOM access means better performance and full context awareness

Verify installation​

After adding the widget:

  1. Open your page in Chrome (or any modern browser)
  2. You should see the Appilot floating button (default: bottom-right)
  3. Click it to open the assistant
  4. Ask a question. If your knowledge base has content for this domain, you'll get a contextual answer

Troubleshooting​

Widget doesn't appear​

  • Check the browser console for errors
  • Check that your CSP (Content Security Policy) allows the Appilot script
  • If you are not using a key: make sure the page is served from a domain registered in the Backoffice (otherwise the backend cannot resolve your organization)
  • If you are using a key: verify it is correct and active

Error responses you may see​

The backend returns these on the widget endpoints:

StatusMeaningFix
401 Provide X-Widget-Key ... or embed on a registered domainNo key was sent and the page domain is not registered (e.g. localhost)Register the domain, or send a wk_test_ key for local dev
401 WIDGET_IDENTITY_REQUIREDThe widget requires an identified user (there is no anonymous usage) and none was providedProvide the user's identity: mint a token via the federation handshake (see Connecting your users), or enable the built-in sign-in. Full decision guide: Identity & Sign-in
Panel shows "Sign in to continue" with no sign-in formNo data-user-token was passed and the built-in sign-in (data-user-auth) is disabledPick an identity source: Identity & Sign-in
403 WIDGET_TEST_KEY_IN_PRODA wk_test_ key whose allowed domains are not all loopback addresses reached the cloudKeep test keys loopback-only, and use a live key on a public hostname. See Develop on localhost
403 WIDGET_DOMAIN_NOT_ALLOWEDThe page origin is not in the key's allowed domains. Test and live keys are both checkedAdd the domain to the key in the Backoffice, or rely on domain resolution and drop the key
429 Rate limit exceededToo many requests in the current minuteBack off; raise the limit on the key if this is expected

CSP configuration​

If your site uses strict CSP headers, add:

script-src 'self' https://cdn.appilot.space;
connect-src 'self' https://api.appilot.space;
style-src 'self' 'unsafe-inline';

For on-premise, replace the Appilot URLs with your self-hosted equivalents.

Next steps​