Search by

citewidget / laravel

citewidget

Add the CiteWidget chat widget to a Laravel site with one Blade tag. Answers come from your own datasheets, with citations.

v0.1.0 2026-11-13 10:38 UTC

This package is auto-updated.

Last update: 2026-11-13 13:31:21 UTC


README

Add the CiteWidget chat widget to a Laravel site with one Blade tag. The widget answers buyers' questions from your own datasheets and links every answer to its source page.

<x-citewidget />

The package prints a single external <script ... defer> tag. It adds no inline JavaScript, needs no build step and runs no JavaScript of its own.

  • Requirements: PHP 8.2 or later, Laravel 12 or 13 (Laravel 13 needs PHP 8.3 or later).

Contents

Install

composer require citewidget/laravel

Laravel discovers the package automatically. Add your public workspace key to .env. Copy it from the Widget page (Embed snippet) or from Settings in your CiteWidget dashboard. It starts with pk_.

CITEWIDGET_KEY=pk_your_public_key

Then put the tag in your layout, just before </body>:

    <x-citewidget />
</body>

The tag can also go in <head>, because the script loads with defer.

Optional: the install command

php artisan citewidget:install

The command does three things:

  • It publishes config/citewidget.php. An existing file is kept unless you pass --force.
  • It adds CITEWIDGET_KEY= and CITEWIDGET_ENABLED=true to .env.example if they are missing.
  • It prints the Blade line to add.

It never reads or writes .env. To publish only the config file, run:

php artisan vendor:publish --tag=citewidget-config

Blade usage

The component and the directive render exactly the same tag.

{{-- Settings from config/citewidget.php and your dashboard --}}
<x-citewidget />
@citewidget

{{-- Override options for one page --}}
<x-citewidget
    position="bottom-left"
    title="Ask our datasheets"
    :suggestions="['Max pressure of DN50?', 'Which seal for steam?']"
    :show-launcher="false"
/>

@citewidget(['position' => 'bottom-left', 'title' => 'Ask our datasheets'])
  • Naming. Blade attributes use kebab-case (primary-color). Directive arrays and the config file use snake_case (primary_color).
  • Binding. To pass booleans, numbers and arrays, use : binding (:show-launcher="false").
  • Overrides. Attributes override the config file, and the config file overrides your dashboard. An attribute you leave out keeps the config value.
  • Misspelt names throw. A misspelt attribute or key, such as positon, throws an exception instead of being ignored.

The rendered tag looks like this:

<script src="https://api.citewidget.com/widget/v1/citewidget.js" data-workspace-key="pk_..."
        data-api-url="https://api.citewidget.com" data-server-url="https://app.citewidget.com"
        data-position="bottom-left" data-navigate-once nonce="..." defer></script>

Opening the chat from your own button

The widget exposes window.CiteWidget with open(), close(), toggle() and newChat(). Call these from your JavaScript bundle rather than from an inline onclick, so a strict CSP is not affected:

document.querySelector('#ask-us')?.addEventListener('click', () => window.CiteWidget.open());

The widget also dispatches citewidget-open, citewidget-close and citewidget-handoff events on document. Their detail is always null.

Configuration

.env Config key Default Notes
CITEWIDGET_KEY key none Your public pk_ key. Anything else renders nothing and logs a warning.
CITEWIDGET_ENABLED enabled true Set it to false to render nothing, for example on staging.
CITEWIDGET_SCRIPT_URL script_url https://api.citewidget.com/widget/v1/citewidget.js https only.
CITEWIDGET_API_URL api_url https://api.citewidget.com Answers API. https only.
CITEWIDGET_SERVER_URL server_url https://app.citewidget.com Account API. Without it, the widget gets no dashboard settings, no lead capture and no "Talk to a person". https only.
— nonce 'auto' See Content Security Policy.
— options.* all null Widget options; see below. null means "not printed".
  • env(). It is called only in config/citewidget.php, so php artisan config:cache works. After you change .env on a server with cached config, run php artisan config:cache again.
  • Precedence. Lowest to highest: the widget's built-in defaults, then your dashboard settings, then the options set through this package (config file, then tag).
  • Managing from the dashboard. Leave an option null to manage it from the dashboard. An option set here overrides the dashboard on every page where it is set.

Options

These are all the options the widget's script tag reads.

Config / directive key Blade attribute Printed as Accepts Widget default
key (config) / workspace_key (tag) workspace-key data-workspace-key Public key: pk_ plus 5–125 of A-Z a-z 0-9 _ - —
api_url api-url data-api-url Absolute https URL https://api.citewidget.com (via this package)
server_url server-url data-server-url Absolute https URL https://app.citewidget.com (via this package)
title title data-title Text, up to 80 characters "Ask our docs"
greeting greeting data-greeting Text, up to 200; the headline of the empty chat "How can I help?"
bot_name bot-name data-bot-name Text, up to 60 "Docs Assistant"
primary_color primary-color data-primary-color Hex colour: #rgb, #rgba, #rrggbb or #rrggbbaa #0F766E
surface_color surface-color data-surface-color Hex colour; panel background in the light scheme #ffffff
color_scheme color-scheme data-color-scheme light, dark or auto (follows the visitor's system) light
position position data-position bottom-right or bottom-left bottom-right
show_launcher show-launcher data-launcher Boolean; false hides the round button (open the chat with CiteWidget.open()) true
launcher_label launcher-label data-launcher-label Text, up to 40 "Ask AI"
launcher_icon launcher-icon data-launcher-icon chat, sparkles, headset or book chat
launcher_icon_url launcher-icon-url data-launcher-icon-url Absolute https image URL, up to 2048 characters; falls back to launcher_icon if it fails to load none
soft_refusal soft-refusal data-soft-refusal Text, up to 500; the reply when your documents have no answer "I don't have this answer in our documents yet."
goodbye goodbye data-goodbye Text, up to 500; shown when the chat ends "Thanks for chatting — goodbye! …"
suggestions suggestions data-suggestions Up to 3 starter questions of up to 200 characters: an array, "Q1|Q2" or a JSON array Used only when the workspace has no suggested questions of its own
handoff handoff data-handoff Boolean; "Talk to a person" (needs server_url) true
handoff_reply_time handoff-reply-time data-handoff-reply-time Text, up to 80, e.g. "usually within a few hours" "usually within a few hours"
lead_capture lead-capture data-lead-capture Boolean; asks for name and email after an unanswered question or at the end of the chat (needs server_url) true
chat_duration_minutes chat-duration-minutes data-chat-duration Whole minutes from 0 to 120; 0 means no limit 0
nonce (tag only) nonce nonce CSP nonce for this one tag From the nonce setting

How values are checked before printing:

  • Booleans. true/false, 1/0, yes/no and on/off are accepted and printed as true or false.
  • Text. Text is trimmed and shortened to the limit. Blank values count as not set.
  • Starter questions. They are printed as a JSON array, with <, >, &, ' and " hex-escaped, inside an HTML-escaped attribute.
  • Colours. This package accepts hex colours only, although the widget itself also understands rgb(), hsl() and colour names.
  • Escaping. Every value is HTML-escaped. Option values are never compiled as Blade, so {{ }} or @php inside a title is printed as text.
  • Invalid values. An invalid value, such as position="top-left", is left out and logged as a warning, and the rest of the tag still renders.

Content Security Policy

Directive Allow Why
script-src https://api.citewidget.com (or the nonce) The widget script
style-src The same nonce (or 'unsafe-inline') The widget copies the script tag's nonce to its own <style>
connect-src https://api.citewidget.com https://app.citewidget.com Answers, dashboard settings, lead capture, "Talk to a person"
img-src blob:, plus the host of launcher_icon_url if you set one Cited PDF pages; your launcher image

The widget loads no frames and no fonts.

If you change script_url, api_url or server_url, allow those hosts instead.

The nonce setting decides which nonce goes on the tag:

nonce setting Nonce on the tag
'auto' (default) Vite::cspNonce() if your app called Vite::useCspNonce(); else app('csp-nonce') from spatie/laravel-csp if it is installed; else none
null None
Any other string That string

A tag can also set its own: <x-citewidget :nonce="$nonce" />. A nonce must be 1–256 characters of A-Z a-z 0-9 + / = _ -; anything else is left out and logged.

Livewire

The tag carries data-navigate-once, so with wire:navigate the script runs once. When Livewire replaces <body>, the widget puts its element back, and an open conversation survives the navigation. Keep <x-citewidget /> in the layout, as usual.

If you prefer Livewire to keep the widget's element itself across navigations, place an empty element for it inside @persist. The widget uses an existing empty <citewidget-chat> instead of creating one.

@persist('citewidget')
    <citewidget-chat></citewidget-chat>
@endpersist
<x-citewidget />

Inertia (React or Vue)

Put <x-citewidget /> in the root template, resources/views/app.blade.php. That template is rendered on the first visit only. The widget adds its element to <body>, outside the Inertia app root, so Inertia page visits leave it in place.

To pass options from your React or Vue code instead, use the @citewidget/react or @citewidget/vue package inside a persistent layout. Use one or the other on a page, not both.

Octane and page caches

The package is safe under Laravel Octane:

  • Config and the nonce are read each time the tag renders.
  • The renderer is never a singleton.
  • No class keeps static state.

Full-page or response caches store the rendered tag, including its nonce. Exclude those pages from the cache, or use a CSP setup whose nonce matches the cached page.

Troubleshooting

Symptom Check
Nothing renders php artisan about --only=citewidget shows whether the widget is enabled and whether the key is accepted. The key must start with pk_. A missing key renders nothing, with a debug log entry. A refused key or invalid option logs a warning that starts with "CiteWidget".
.env change has no effect Config is cached: run php artisan config:cache (or config:clear).
Dashboard changes don't show An option set in config/citewidget.php or on the tag overrides the dashboard. Set it back to null or remove the attribute. Also check that server_url is not blank.
No lead capture or "Talk to a person" Both need server_url.
Script or styles blocked See Content Security Policy. The browser console names the blocked directive.
Unknown CiteWidget option / Unknown <x-citewidget> attribute(s) A misspelt name; the message lists the valid ones.

Security

  • Public key only. Only a public pk_ key belongs in a page. The package refuses anything else, such as a secret sk_ key: it renders nothing, and its log message never repeats the refused key.
  • The key is visible. A public key can be read from your page source, like any embed key. Restricting a key to your own domains is planned for the CiteWidget dashboard. Set your allowed domains there once that ships.
  • Validation and escaping. URLs must be absolute https URLs without credentials. Every printed value is validated and HTML-escaped.

Development

composer install
composer test      # Pest
composer analyse   # PHPStan (Larastan), level max
composer lint      # Pint --test

tests/Fixtures/embed-attributes.json is a copy of the CiteWidget embed contract. The tests fail when the option map drifts from it.

Changelog

See CHANGELOG.md.