---
title: "Governed chat widget"
description: "Embed Norcaster Boundary on your site: script tag, React, Vue, and Svelte packages. Browser keys, CORS, and governed POST /run per message."
canonical: https://www.norcaster.com/docs/chat-widget
---
<!-- Generated at build time from the rendered page by portal/scripts/generate-llms.mjs. The page is the source of truth; edit it, not this file. -->

Deploy · Control

# Governed chat widget

Add a protected chat surface to your site. Each send is an independent governed `POST /run` — Boundary checks the prompt before provider inference. Use a script tag or an npm adapter; all paths share the same core.

## Choose an integration

- **Script embed** — fastest; load `https://www.norcaster.com/chat/v1.js` from a script tag.
- **npm adapters** — React, Vue, or Svelte components on `@norcaster/chat-*`.
- **SDK / API** — full control via `POST /run` from your backend or [client SDK](https://www.norcaster.com/docs/policies).

## Script embed

Norcaster serves the widget at `https://www.norcaster.com/chat/v1.js` (built from `@norcaster/chat-core`).

Inline (mount in your page)

```
<!-- Try it live -->
<div id="norcaster-chat-root"></div>
<script
  src="https://www.norcaster.com/chat/v1.js"
  data-deployment="YOUR_DEPLOYMENT_ID"
  data-api-key="YOUR_DEPLOYMENT_KEY"
  data-mode="inline"
  data-theme="dark"
  data-greeting="Messages are checked by Norcaster before the model."
  async
></script>
```

Floating launcher

```
<script
  src="https://www.norcaster.com/chat/v1.js"
  data-deployment="YOUR_DEPLOYMENT_ID"
  data-api-key="YOUR_DEPLOYMENT_KEY"
  data-position="bottom-right"
  async
></script>
```

Script attributes

| Attribute | Required | Notes |
| --- | --- | --- |
| data-deployment | true | Deployment id from the dashboard. |
| data-api-key | one of key or runUrl | Key that can only run: from the deployment's Connect tab, or runs:write only. Visible to every visitor. |
| data-run-url | one of key or runUrl | Same-origin proxy URL; session cookie auth instead of api-key. |
| data-api-url | false | API base (default https://api.norcaster.com). |
| data-mode | false | inline (mount in #norcaster-chat-root) or floating (default). |
| data-target | false | CSS selector for inline mount (default #norcaster-chat-root). |
| data-position | false | bottom-right or bottom-left (floating). |
| data-theme | false | dark, light, or auto. |
| data-greeting | false | First line shown in the transcript. |
| data-provider | false | Optional gateway routing metadata. |
| data-model | false | Optional gateway routing metadata. |
| data-lazy | false | true or 1 to defer init until visible. |

## npm packages

Framework adapters wrap `@norcaster/chat-core` — they do not reimplement chat logic. Published on npm under the `@norcaster` scope.

- `@norcaster/chat-core` — Vanilla mountNorcasterChat / embed IIFE source
- `@norcaster/chat-react` — React 18+ <NorcasterChat />
- `@norcaster/chat-vue` — Vue 3 <NorcasterChat />
- `@norcaster/chat-svelte` — Svelte 5 <NorcasterChat />

React (Next.js App Router)

```
pnpm add @norcaster/chat-react @norcaster/chat-core

// components/norcaster-chat.tsx
"use client";
import { NorcasterChat } from "@norcaster/chat-react";

export function NorcasterChatPanel() {
  return (
    <NorcasterChat
      deploymentId={process.env.NEXT_PUBLIC_NORCASTER_DEPLOYMENT_ID!}
      apiKey={process.env.NEXT_PUBLIC_NORCASTER_PUBLISHABLE_KEY!}
      theme="dark"
      greeting="Try a message — Norcaster checks it before the model."
    />
  );
}
```

Vue 3 (Nuxt)

```
pnpm add @norcaster/chat-vue @norcaster/chat-core vue

<script setup lang="ts">
import { NorcasterChat } from "@norcaster/chat-vue";
</script>

<template>
  <ClientOnly>
    <NorcasterChat
      deployment-id="YOUR_DEPLOYMENT_ID"
      :api-key="deploymentKey"
      theme="dark"
    />
  </ClientOnly>
</template>
```

Svelte 5 (SvelteKit)

```
pnpm add @norcaster/chat-svelte @norcaster/chat-core svelte

<script lang="ts">
  import { browser } from "$app/environment";
  import { NorcasterChat } from "@norcaster/chat-svelte";
</script>

{#if browser}
  <NorcasterChat
    deploymentId="YOUR_DEPLOYMENT_ID"
    apiKey="YOUR_DEPLOYMENT_KEY"
  />
{/if}
```

Shared props: `deploymentId`, `apiKey`, `apiUrl`, `runUrl`, `theme`, `position`, `greeting`, `provider`, `model`, `mode`, `lazy`. Adapters mount only in the browser (SSR-safe).

## Keys, CORS, and security

- **Browser keys** — every visitor can read a key in HTML or a client bundle, and Norcaster has no separate publishable key type. If the key must be in the page, use one that can only run requests: a key from the deployment's Connect tab (bound to that one deployment, `runs:write`), or a personal key with only `runs:write` (it can run all your deployments). Set a budget on the deployment and revoke the key if it leaks. Never embed a key with other scopes.
- **Server proxy (recommended)** — set `runUrl` to a same-origin route that forwards to Norcaster with your server-held key or session cookie.
- **CORS** — cross-origin widgets need each site origin in the API `CORS_ORIGINS` env var (comma-separated).
- **Transcript model** — each send is one governed run; the widget does not maintain ChatGPT-style multi-turn memory in the UI today.

## Portal sandbox bot protection

The public [Boundary sandbox](https://www.norcaster.com/boundary/sandbox) and homepage hero demo can require Cloudflare Turnstile when your **portal** deployment sets both `NEXT_PUBLIC_TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY`. That protects Norcaster-hosted demos — not customer sites embedding the widget. Create keys in the Cloudflare dashboard → Turnstile → add widget domains (localhost, production host).

## Related

- [Boundary enforcement](https://www.norcaster.com/docs/boundary-enforcement) — policies and budgets on the request path.
- [Test Boundary live](https://www.norcaster.com/boundary/sandbox) — try prompts without signup.
- [Runtime policies](https://www.norcaster.com/docs/policies) — packs, redaction, and CLI.
