
Research
/Security News
PolinRider Spreads Through Compromised GitHub Accounts and Packagist
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.
@matterchat/svelte
Advanced tools
The Matter Chat support widget for Svelte and SvelteKit. One call, no snippet.
The Matter Chat support widget for Svelte and
SvelteKit. One call, no snippet, no <svelte:head> script to hand-write.
Also available: @matterchat/react, @matterchat/vue, @matterchat/angular.
pnpm add @matterchat/svelte
Call loadMatterChat once from onMount in your root layout. It runs only in
the browser, so it is safe in a layout that also renders on the server.
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { onMount } from "svelte";
import { loadMatterChat } from "@matterchat/svelte";
import { PUBLIC_MATTERCHAT_KEY } from "$env/static/public";
onMount(() => {
loadMatterChat({ publicKey: PUBLIC_MATTERCHAT_KEY });
});
</script>
<slot />
That is legacy syntax, which Svelte 5 still accepts. In a runes-mode layout the call is identical; only the children change:
<!-- src/routes/+layout.svelte (Svelte 5, runes) -->
<script lang="ts">
import { onMount } from "svelte";
import { loadMatterChat } from "@matterchat/svelte";
import { PUBLIC_MATTERCHAT_KEY } from "$env/static/public";
let { children } = $props();
onMount(() => {
loadMatterChat({ publicKey: PUBLIC_MATTERCHAT_KEY });
});
</script>
{@render children()}
If you would rather attach it to markup, matterChat is a Svelte action. The
element it is used on is left untouched — the widget lives in its own DOM, not
inside your host.
<script lang="ts">
import { matterChat } from "@matterchat/svelte";
import { PUBLIC_MATTERCHAT_KEY } from "$env/static/public";
</script>
<div use:matterChat={{ publicKey: PUBLIC_MATTERCHAT_KEY }}></div>
Your public key is in Setup in the dashboard. It is safe in client code: it identifies the bot, it does not authorise anything.
Both routes share one function. Neither renders anything of its own; each is a side effect, and the widget draws itself.
If you know who the visitor is, you can tell the widget — but only your server
can make that claim believable. id on its own is a label the visitor could
have typed; hash is your backend vouching for it.
<script lang="ts">
import { onMount } from "svelte";
import { loadMatterChat } from "@matterchat/svelte";
import { PUBLIC_MATTERCHAT_KEY } from "$env/static/public";
export let data; // from +layout.server.ts
onMount(() => {
loadMatterChat({
publicKey: PUBLIC_MATTERCHAT_KEY,
user: { id: data.user.id, hash: data.chatHash, expiresAt: data.chatHashExpiresAt },
});
});
</script>
hash is an HMAC over <id>:<expiresAt> keyed on the bot's identity secret.
That secret must never reach the browser — compute the hash in a load
function on the server and pass the result down. Without a hash that verifies,
the visitor is treated as anonymous.
Requires the Verified visitor identity feature on your plan.
Both loadMatterChat(options) and use:matterChat={options} take the same
object.
| Option | Type | |
|---|---|---|
publicKey | string | Required. The bot's public key. |
user | { id, hash, expiresAt } | Verified visitor identity. Omit for anonymous. |
appUrl | string | Defaults to https://app.matterchat.co. For a preview or self-hosted install. |
There are deliberately no appearance options — no title, greeting, accent, position or suggestions. All of it is configured in the dashboard and delivered with the bot's config. An option that overrode the dashboard would be a second source of truth, and the first support ticket would begin "I changed the colour and nothing happened".
Injects one <script> pointing at a content-hashed loader, with a matching
integrity hash and crossorigin="anonymous", then queues init so ordering
never matters. The loader asks the app which widget bundle is current and adds
it with the hash the server supplies for it, so your browser still refuses to
run a bundle whose bytes do not match a hash — the hash simply arrives per page
load rather than being frozen into this package.
It is safe to call in more than one place — the script is added once, and the
widget ignores a second init. The action's update re-queues init only
when the key or the visitor actually changes, so a parent re-render that
rebuilds the options object does nothing. Its destroy is intentionally a
no-op: tearing the chat out mid-conversation on a route change, and
re-downloading the bundle on every client-side navigation, are both worse than
leaving it.
loadMatterChat returns early when there is no window, so calling it during
server-side rendering is harmless — but onMount is still the right place,
because the widget only needs to load once per page, not once per render.
Each release pins one loader, not one widget build. Widget fixes and features reach you without a release of this package, because the loader resolves the current bundle at run time. What you give up is that we can change the widget's code underneath you between releases. What you keep is the part that stops a tampered CDN: nothing executes unless its bytes match a hash.
WIDGET_LOADER is exported for inspection only — the package reads it for you.
Svelte 4 or 5, or no Svelte at all: the package imports nothing from svelte
at runtime, which is why the peer dependency is optional. No dependencies.
FAQs
The Matter Chat support widget for Svelte and SvelteKit. One call, no snippet.
We found that @matterchat/svelte demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.