# Plain HTML and other single-page apps

Add a Web Agent to a site that has no framework, or to an app built with SolidJS, Preact or Alpine.js. The loader is plain JavaScript with no framework dependency, so the install is the same everywhere: a script tag, or the npm package where the app has a bundler. At the end, the agent's launcher is on your pages and the agent knows which user is signed in.

## Prerequisites

- A page you can add a script tag to, or an app with a bundler.
- A Web Agent in your workspace whose analysis has finished, with a version deployed to Production.

## 1. Copy your snippet

1. In the portal, go to [**Web Agents**](https://auth.faustine.ai/go?to=%2Fweb-assistants) and select your agent.
2. In the **Overview** tab, find the **Install the assistant** card and select the **JS / Manual** tab.
3. Select **Copy**.

## 2. Add the script tags

Paste the snippet into every page, before `</body>`. On a site with a shared layout or template, paste it there once.

```html filename="index.html"
<script src="https://webagent.faustine.ai/loader.js"></script>
<script>
  Faustine.init({ agentId: '<AGENT_ID>' });
</script>
```

The first tag defines `window.Faustine`. The second starts the agent. `init` loads the Production environment. To load another one, pass its name: `env: 'Staging'`.

If the site has no signed-in users, the **JS / Auto** tab has a shorter snippet that starts the widget by itself. See [Install with a script tag](https://docs.faustine.ai/web-agents/script-tag/).

## 3. Identify the signed-in user

Call `Faustine.identify` when the page knows who the user is. This example asks your backend and passes a display name and email, which appear in the agent's **Monitoring** tab as **Unverified**.

```html filename="index.html"
<script src="https://webagent.faustine.ai/loader.js"></script>
<script>
  Faustine.init({ agentId: '<AGENT_ID>' });

  fetch('/api/me')
    .then((res) => (res.ok ? res.json() : null))
    .then((me) => {
      if (me) {
        Faustine.identify({ userId: me.id, name: me.name, email: me.email });
      }
    });
</script>
```

`/api/me` stands for your own endpoint that returns the signed-in user. On a page your server renders, you can write the values into the call instead of fetching them.

In your sign-out handler, call `Faustine.logout()` before you leave the page. It removes the identity and the open conversation from the browser.

For production, replace the labels with a token signed by your backend. See [Verify your users with a signed token](https://docs.faustine.ai/web-agents/verify-users/).

## 4. Open your site

Load a page that has the snippet.

> **Checkpoint:** The agent's launcher appears on the page. Select it and send a message. In the portal, the conversation appears under the agent's **Monitoring** tab.

## Install from npm in SolidJS or Preact

In an app with a bundler, install the package and start the agent from a module. The calls are the same as with the script tag, and they are typed.

**npm**

```bash
npm install @faustine-ai/web-agent
```

**pnpm**

```bash
pnpm add @faustine-ai/web-agent
```

**yarn**

```bash
yarn add @faustine-ai/web-agent
```

**bun**

```bash
bun add @faustine-ai/web-agent
```

```ts filename="src/faustine.ts"
import Faustine from '@faustine-ai/web-agent';

export const agent = Faustine.init({
  agentId: '<AGENT_ID>',
});
```

Import the module from the entry file, and identify the user from the root component. `useSession` stands for your own session code.

**SolidJS**

```tsx filename="src/index.tsx"
import { render } from 'solid-js/web';
import App from './App';
import './faustine';

render(() => <App />, document.getElementById('root')!);
```

`user` is a signal. The effect runs again each time it changes.

```tsx filename="src/App.tsx"
import { createEffect } from 'solid-js';
import { agent } from './faustine';
import { useSession } from './session';

export default function App() {
  const { user } = useSession();
  let signedIn = false;

  createEffect(() => {
    const current = user();
    if (current) {
      agent.identify({ userId: current.id, name: current.name, email: current.email });
      signedIn = true;
    } else if (signedIn) {
      agent.logout();
      signedIn = false;
    }
  });

  return <main>Your app</main>;
}
```

**Preact**

```tsx filename="src/main.tsx"
import { render } from 'preact';
import { App } from './App';
import './faustine';

render(<App />, document.getElementById('app')!);
```

`user` is a value. The effect runs again when it changes between renders.

```tsx filename="src/App.tsx"
import { useEffect, useRef } from 'preact/hooks';
import { agent } from './faustine';
import { useSession } from './session';

export function App() {
  const { user } = useSession();
  const signedIn = useRef(false);

  useEffect(() => {
    if (user) {
      agent.identify({ userId: user.id, name: user.name, email: user.email });
      signedIn.current = true;
    } else if (signedIn.current) {
      agent.logout();
      signedIn.current = false;
    }
  }, [user]);

  return <main>Your app</main>;
}
```

Both examples call `logout` only after a user has been identified, so a visitor who never signs in keeps their conversation.

## Call the agent from Alpine.js

An Alpine.js site uses the script tags of step 2. `Faustine` is a global, so an Alpine expression can call it. This button opens and closes the agent's panel.

```html filename="index.html"
<button type="button" x-data @click="Faustine.toggle()">Help</button>
```

To replace the agent's launcher with a button like this one, see [Use your own launcher button](https://docs.faustine.ai/web-agents/custom-launcher/).

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `Faustine is not defined` in the console | The loader tag has `async` or `defer`, or the inline script comes before it. | Load `loader.js` with a plain script tag, before the script that calls `Faustine.init`. |
| No launcher, and the request to `webagent.faustine.ai/embed` returns `404` | No environment of the agent serves a version. | [Publish a version](https://docs.faustine.ai/web-agents/publish-a-version/). |
| No launcher, and the same request returns `403` | The page's domain is not in the environment's allowed domains. | See [Restrict the domains that can load the agent](https://docs.faustine.ai/web-agents/restrict-domains/). |
| The user stays anonymous on a page with the **JS / Auto** tag | `Faustine.identify` needs `Faustine.init`, which that tag does not call. | Use the **JS / Manual** snippet. |

## Next steps

- [Verify your users with a signed token](https://docs.faustine.ai/web-agents/verify-users/): give users their history on every device.
- [Restrict the domains that can load the agent](https://docs.faustine.ai/web-agents/restrict-domains/): allow your own domains only.
- [Install with a script tag](https://docs.faustine.ai/web-agents/script-tag/): the two script-tag snippets and when to use each.
- [JavaScript API](https://docs.faustine.ai/reference/javascript-api/): every option of `init` and every method of the handle.
