USE CASE

Laravel AI support chat widget

Add Deacon to a Laravel app with one line in your Blade layout. It answers visitors from your docs and says so when it doesn’t know.

By the Deacon team

Published

Deacon is an AI support chat for Laravel apps, installed with one line in a Blade layout. It answers from your docs and pages in a few seconds. Faced with a question they don’t cover, it tells the person it doesn’t know, then offers an email box, or, when your layout has passed it the logged-in user, a reply in one tap. If they leave an address, Deacon emails you their question unless you’re already looking at the dashboard, and your answer from there appears in their chat. Start on the free plan, with 50 answers a month, no card and no end date.

We read the Laravel 13 and Inertia v3 documentation on 25 October 2026, when Laravel 13.38 was current. Deacon is itself a Laravel and Inertia app, and its own widget sits in its root template exactly where the Inertia section below puts yours. Coding agents can read this page as plain Markdown.

Before touching a Blade file

Register with Deacon and copy the snippet from Install, where data-key holds your pk_ key. Install’s list of domains needs the app’s production domain, which is there already if you gave it at sign-up, subdomains and www included. Composer and npm stay out of it, as there’s no package.

A Laravel app is your own code to Install, so its steps offer one prompt. Copy the prompt, paste it into Claude Code, Cursor or Codex, and the agent adds the code and checks it’s live, while you read on.

Which Blade file gets the tag

The snippet goes above </body> in whichever Blade file wraps every page, and that depends on how the app began.

React, Vue or Svelte starter kits, and other Inertia apps

An Inertia app renders every page into one root template, resources/views/app.blade.php. Inertia’s server-side setup guide says it’s “loaded on the first page visit”. After that Inertia swaps pages without a reload, so the script starts once and keeps the chat open as people move around, and on the live site it counts each visit to a new page. Only the <script> line below is new.

{{-- resources/views/app.blade.php, the end of the file --}}
<body class="font-sans antialiased">
    <x-inertia::app />
    <script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
</body>

The Livewire starter kit

Laravel’s starter kit docs keep the Livewire kit’s layouts in resources/views/layouts, with a sidebar and a header layout for the app and three variants for the log-in screens. Each one closes its own <body>, but all of them include resources/views/partials/head.blade.php. Add the tag to the end of that partial and every one of those screens loads it, since Deacon’s script runs from <head> as happily as from the body. welcome.blade.php stands alone, so give it the tag above its own </body>.

Your own Blade layout

The Blade docs build a layout either as a component, such as resources/views/components/layout.blade.php, or as a view the others extend. The tag goes before that file’s </body>.

Checking it on your machine

The address to watch is the one PHP serves the page from, not Vite’s. composer run dev starts both, but the page is on http://localhost:8000, or on the app’s .test address if Laravel Herd serves it.

  1. Open the app there. The chat stays hidden on the first load, because that address isn’t on Install’s list yet.
  2. In Deacon’s dashboard, Install now reads “Found the code on localhost:8000”, or names the .test address, and adds either with one click. Deacon treats both as tests, so chats there are never billed.
  3. Reload. A button labelled Help, or your own label from Appearance, sits in the corner. Ask it something your docs cover.
  4. After you deploy, Install shows the production domain as Live and when Deacon was last seen there.

An agent working in the repository can check from the terminal. In the answer, latest is the newest address to load Deacon and refused a development address it turned away, as Deacon’s install guide describes.

curl -s "https://heydeacon.com/api/widget/seen?k=pk_your_widget_key"

Passing the logged-in user

In an app with logins, give Deacon each person’s id, email and name. You’ll see who is asking in Conversations, and someone Deacon can’t answer gets a one-tap reply instead of typing their address. The details are only a label. Deacon doesn’t verify them, and they’re never sent to the AI. Put this block above the tag, in the same file. On a page where nobody is logged in, such as the one log-out lands on, it prints null, and the next person on that computer starts fresh.

{{-- the same layout, just above Deacon's tag --}}
@auth
    <script>
        window.deaconUser = {{ Js::from([
            'id' => auth()->id(),
            'email' => auth()->user()->email,
            'name' => auth()->user()->name,
        ]) }};
    </script>
@else
    <script>window.deaconUser = null;</script>
@endauth

Js::from makes the JSON, in the Blade docs’ words, “properly escaped for inclusion within HTML quotes”. We rendered the block in Laravel 13 on 25 October 2026 with </script> inside a name, and it came through intact. Avoid @json with an array written out inside it. Blade splits that directive’s argument at every comma, so an array of three keys loses the escaping @json normally adds, and one with a trailing comma doesn’t compile.

Inertia logins and log-outs

Inertia logs people in and out without reloading the root template, so the @auth block only describes the first page. Keep Deacon current from the client as well. Laravel’s starter kits share the user as auth.user, and Inertia’s navigate event fires after every visit. Add this to resources/js/app.tsx, or app.ts.

// resources/js/app.tsx, after createInertiaApp()
import { router } from '@inertiajs/react'; // or '@inertiajs/vue3', '@inertiajs/svelte'

router.on('navigate', (event) => {
  const user = event.detail.page.props.auth.user;

  window.deaconUser = user ? { id: user.id, email: user.email, name: user.name } : null;
  window.Deacon?.identify(window.deaconUser);
});

A null after a log-out starts the next person on that browser fresh. For TypeScript, the window.deaconUser and window.Deacon declaration is in Deacon’s install guide, along with how to pass each user’s plan.

What your app’s visitors get, and what you learn

Logged-in customers ask from inside the product and get an answer in seconds. When they want a person, Deacon tells them the team has been told, and your reply arrives in the same chat under your name.

The dashboard turns those chats into a to-do list. Questions Deacon couldn’t answer wait with an Answer this button, Topics counts how many people asked about each thing, and bug reports sit beside the JavaScript errors your pages throw, each ready to copy for your coding agent.

Laravel questions

Can the key live in config instead of the template?

It can, though there’s no need to hide it. Every visitor’s browser reads the key, and the widget only shows on the domains listed on Install. To keep it in .env anyway, read it in the template through a config file, like any other setting.

The tag is in, but no chat button shows. Why?

The browser console says, in a line that starts [Deacon]. “isn’t in your allowed domains” means the address isn’t on Install’s list, often a .test site you haven’t added yet, “Unknown widget key” means data-key doesn’t match your snippet, and “could not start” ends with the error that stopped it. With no [Deacon] line at all, the page isn’t using the layout you edited, as with the Livewire kit’s welcome.blade.php, or the button is hidden in Appearance. If the app sends a Content Security Policy, Deacon’s origin, https://heydeacon.com, belongs beside 'self' in script-src, connect-src and frame-src, for the script, its requests and the chat window, and style-src needs 'unsafe-inline', since the script writes its styles into its own part of the page, where a nonce can’t reach. A missing frame-src doesn’t hide the button. It leaves the chat blank when opened, with a console line saying the chat frame “did not load”.

Try it on your own documentation

Add your help pages, ask it the question you know your docs cannot answer, and watch it say so.

The free plan covers 50 answers a month on one seat, and the paid plans are on the pricing page.

Start free

Free plan, no card.

READ NEXT