npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@matterchat/svelte

v0.3.1

Published

The Matter Chat support widget for Svelte and SvelteKit. One call, no snippet.

Readme

@matterchat/svelte

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

SvelteKit

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()}

As an action

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.

Signed-in visitors

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.

Headless: your own input, the bot's answers

The same package also exports useMatterChat, for a help page or docs site that wants an ask box instead of a launcher. Same public key, same knowledge, same inbox.

<script lang="ts">
  import { useMatterChat } from "@matterchat/svelte";

  const chat = useMatterChat({ publicKey: "pk_live_…" });
  let q = "";
</script>

<form on:submit|preventDefault={() => { chat.ask(q); q = ""; }}>
  <input bind:value={q} placeholder="Ask anything" disabled={$chat.streaming} on:focus={chat.warm} />
  {#if $chat.status === "connecting"}<p>Connecting…</p>{/if}
  {#each $chat.messages as m}<p>{m.content}</p>{/each}
  {#if $chat.error}<p role="alert">{$chat.error.message}</p>{/if}
</form>

useMatterChat returns a readable store, so $chat gives you messages, streaming, error, conversationId, sources and status.

The question shows at once and the answer grows token by token. Each assistant message carries sources (the pages it cited, once each), retrieved (what its [n] markers point at), refusal, lowConfidence and handoff. chat.rate(1 | -1) rates the latest answer and chat.reset() starts over, stopping an answer still streaming. Leaving the page does the same, so an abandoned answer does not spend a reply. A question that fails stays on screen with failed: true, and asking again replaces it.

Nothing connects until warm runs or the first question is asked; status says connecting meanwhile. parseBlocks is re-exported for rendering an answer's Markdown the way the widget does. Full reference in @matterchat/core's README and at https://matterchat.co/docs/headless.

Options

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".

What it does

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.

Versioning

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.

Requirements

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.