n8n's chat widget: what people run into, and a replacement that takes the same call
The problems n8n users report most with @n8n/chat — styling, a bubble full of JSON, tool steps after a reload, sessions, accessibility — what n8n 3.0 changes, and a createChat that takes the same options, tested against a live n8n.
Published October 27, 2026.
n8n’s chat widget, @n8n/chat, is how most n8n chat
workflows reach a website: a <link>, a createChat({ webhookUrl }), and a bubble in the corner
talks to the workflow’s Chat Trigger. It is used a lot: 62,029 npm downloads in the month to
October 22, 2026, and 73 million jsDelivr hits a month — n8n’s own hosted chat page loads it from
jsDelivr, which likely accounts for much of that. And its community forum keeps returning to the
same few problems. This page lists them with their sources, says what n8n 3.0 changes, and shows a
replacement we built and tested against a live n8n,
@aparte/n8n-chat: it takes the same call.
What people run into
Section titled “What people run into”Styling stops at CSS variables. The most viewed thread about the widget asks how to customise
its window (8,843 views).
In another, the answer is plain: “We only have the option to change the root variables”; past them,
the advice is to fork the project
(CSS Customization of /chat integration).
The widget ships 115 --chat--* variables; beyond them, its classes are not a documented API, and
some of its rules carry Vue’s per-build attribute hashes.
A bubble full of JSON. When the last node’s answer has no output, text or message field,
the widget prints the whole response into the bubble
(Chat embed returns JSON). The workflow
is right there to fix, but nothing tells the developer which field is missing.
Tool steps after a reload. With Load Previous Session, the history comes back from the agent’s memory, which — with recent AI Agent versions — stores a tool’s call and its result beside the messages, and the widget draws them as bot bubbles, the result as raw JSON (after page reload, tool calls show up as AI messages).
No way to start over. With the history on, which is the default, the widget reuses the session
stored in the browser: the source of startNewSession keeps it on purpose, and “how do I reset
the conversation” is one of the forum’s most read questions
(2,104 views).
createChat returns the Vue app, so a page has no documented way to open, close, send or reset the
chat other than clicking its elements.
Every page view asks n8n. With the history on, the widget requests it when the page loads, whether or not the visitor ever opens the chat — on n8n Cloud, an execution per page view (Embedded chat costs a lot even when not used).
Keyboard and screen readers. The widget’s components carry no role, aria-* or tabindex:
the button in the corner is a div with a click handler, and its send button has no name. Its
visitor’s bubble, white on #20b69e, reads at 2.55:1, where text needs 4.5:1.
And the rest: English only, with eight strings you can override; no dark scheme; global rules
such as .hljs and .empty, and an h1 title in your page; about 465 KB of JavaScript in four
files, with brotli, for the first render; and the
Sustainable Use License, which allows
distribution only free of charge for non-commercial purposes.
What n8n 3.0 changes for a chat widget
Section titled “What n8n 3.0 changes for a chat widget”n8n’s 3.0 breaking changes concern an
embedded chat widget in one place: a Chat Trigger that answers through response nodes (the Chat
node, for human-in-the-loop flows) talks over a WebSocket, and its frames become JSON. @n8n/chat
understands both formats from version 1.31.0, so a page pinned to an older version, or a custom
client, has to move. The HTTP path — a message posted to the trigger, the answer streamed back as
newline-delimited JSON, the history loaded with loadPreviousSession — is not on the list. The
replacement below reads both WebSocket formats too.
A replacement that takes the same call
Section titled “A replacement that takes the same call”@aparte/n8n-chat is n8n’s widget drawn by aparté. It is
open source, on npm, and served by jsDelivr; the page changes
two URLs:
<link href="https://cdn.jsdelivr.net/npm/@aparte/n8n-chat@0.1.0/dist/style.css" rel="stylesheet" />
<script type="module">
import { createChat } from 'https://cdn.jsdelivr.net/npm/@aparte/n8n-chat@0.1.0/dist/cdn/n8n-chat.js';
createChat({ webhookUrl: 'https://your-n8n.example.com/webhook/<id>/chat' });
</script>It keeps what a page relies on: every option and its default, the session in the same
localStorage key (a visitor keeps their conversation across the switch: we saw both widgets load
the same one on the same page), the history, files sent as files, disabled, the Repost and Reuse
actions, messageComponents (as functions rather than Vue components), the arrow keys that bring a
sent message back, highlighted code, and n8n’s main --chat--* variables, which its n8n look reads.
And it answers the list above:
createChatreturns a handle:open(),close(),toggle(),send(),reset()— which starts a new session —setDisabled()anddestroy().- In a window, the history is asked for when the chat first opens: a page view alone reaches no n8n execution.
- A last node with no
outputgets an error that names the fields it returned, not a bubble of JSON; a visitor reads a short sentence in their language — with the workflow’s own words under it when the workflow reported the error — and the console tells the developer what to change in n8n. - The history leaves out a tool’s call and its result, as the live answer does.
- The button in the corner is a button with a name, the window is a labelled region, a phone’s full-screen chat is a modal dialog, the visitor’s bubble reads at 4.7:1, and there is no heading in your page.
- Ten languages ship — the page downloads the one it uses — and a dark scheme. About 100 KB of JavaScript and 22 KB of CSS with brotli for the first load, the highlighter joining with the first code block. MIT.
It also takes the response-node path: a Chat node’s messages arrive one by one, a Chat node that
waits for a written reply gets the visitor’s next message, and one that waits for an approval shows
its buttons — with Block User Input deciding whether the visitor may write instead, as in n8n’s
widget. On n8n 2.x, Chat nodes run only when the Chat Trigger is in Hosted Chat mode. What does
not carry over is CSS written against n8n’s .chat-* classes; the variables do.
How this was checked
Section titled “How this was checked”- Against a live n8n. n8n 2.44.6 ran a public Chat Trigger with an AI Agent, a Simple Memory and a Calculator, on a real model (Gemma 3n E2B, served by LM Studio’s OpenAI-compatible endpoint). Its answers were recorded byte for byte — a streamed turn, a tool turn, a turn that read the memory, a text file uploaded in a form, a model refusing an image, a whole answer, the history — and are replayed in the runner’s tests. The response-node path was recorded the same way on n8n 2.44.7: the WebSocket’s frames, a button’s link, a written reply.
- What the recordings taught. A stream can open on a
keepaliveframe when a slow node runs first; a reader that decides from the first line would take the whole stream for one answer. Every answer is labelledapplication/json, streamed or not. Loading the history from memory fails with an error 500 unless the memory is connected to the Chat Trigger as well as to the agent. And on the socket, every message is followed byn8n|continueexcept the one after which the execution waits for the visitor — which is how a client knows the turn is over. - Side by side. Both widgets ran on the same page against the same workflows, in Chromium, option by option.
- Audited. Independent reviews — the code, these claims, and the widget in a browser with axe — before this article’s first draft; what they found is fixed. The response-node path came after, and was checked against the live n8n, not audited.
- Tests. The runner has 95, the widget 54, in a DOM over a scripted n8n: the options, the session, the history, the handle, the hooks, the languages, the buttons.
- Not checked: n8n Cloud, and n8n 3.0, which is not released yet. The nine languages beyond English have not been read by native speakers.
Versions: @n8n/chat 1.43.3, n8n 2.44.6 and 2.44.7, @aparte/n8n-chat 0.1.0 on aparté 0.27.4.
Sources
Section titled “Sources”- npm,
@n8n/chat: version 1.43.3, its license, and downloads; jsDelivr, monthly hits. - n8n community: How to customise the chat window, CSS Customization of /chat integration, Chat embed returns JSON, tool calls after a reload, resetting the conversation, embedded chat costs even when not used.
- n8n on GitHub: the widget’s source.
- n8n docs, v3.0 breaking changes.
- aparté:
@aparte/n8n-chat, its repository, and the n8n runner.