Skip to main content

Svelte Quickstart

Five minutes from clone to working chat in Svelte. Install, seed, wire the provider, drop in the library and chat components. Done.

1. Install

bash
npm install @cstar.help/svelte

2. Seed sample data

Empty workspaces aren't fun to test against. Populate yours with realistic content first:

bash
cstar seed

Creates 5 categories, 12 articles, 3 customers, and 5 tickets. Full breakdown over at the quickstart.

3. Wire up the providers

+layout.svelte
<!-- src/routes/+layout.svelte -->
<script>
  import { CStarChatProvider, CStarLibraryProvider } from '@cstar.help/svelte';

  let { children } = $props();
</script>

<!-- Two providers: Chat (authenticated) and Library (public) -->
<CStarLibraryProvider teamSlug="acme">
  <CStarChatProvider teamSlug="acme">
    {@render children()}
  </CStarChatProvider>
</CStarLibraryProvider>

4. Browse the Knowledge Base

List categories and articles from your public help center.

+page.svelte
<!-- src/routes/help/+page.svelte -->
<script>
  import { getLibraryClient, CategoriesState, ArticlesState } from '@cstar.help/svelte';

  const client = getLibraryClient();
  const cats = new CategoriesState(client);
  const articles = new ArticlesState(client, { limit: 5 });
</script>

<h2>Help Center</h2>

{#if cats.isLoading}
  <p>Loading...</p>
{:else}
  {#each cats.categories as cat (cat.id)}
    <div>
      <h3>{cat.name} ({cat.article_count})</h3>
    </div>
  {/each}
{/if}

<h2>Latest Articles</h2>
{#each articles.articles as a (a.id)}
  <a href="/help/{a.slug}">{a.title}</a>
{/each}

5. Add Customer Chat

Let customers open tickets and send messages in real-time.

ChatWidget.svelte
<!-- src/lib/components/ChatWidget.svelte -->
<script>
  import { onDestroy } from 'svelte';
  import {
    getChatClient, ChatState, TicketsState, MessagesState
  } from '@cstar.help/svelte';

  const client = getChatClient();
  const chat = new ChatState(client);
  const tix = new TicketsState(client);

  let activeThread = $state(null);
  let draft = $state('');

  async function startChat() {
    // Get HMAC signature from your server
    const res = await fetch('/api/cstar-auth');
    const { signature, customer } = await res.json();
    await chat.identify(customer, signature);

    const ticket = await tix.create({ title: 'Help needed' });
    activeThread = new MessagesState(client, ticket.id);
  }

  async function handleSend() {
    await activeThread.send(draft);
    draft = '';
  }

  onDestroy(() => activeThread?.destroy());
</script>

{#if !chat.isIdentified}
  <button onclick={startChat}>Chat with us</button>
{:else if activeThread}
  {#each activeThread.messages as m (m.id)}
    <p><b>{m.sender_name}</b>: {m.content}</p>
  {/each}
  <input bind:value={draft} />
  <button onclick={handleSend}>Send</button>
{/if}

Full-text article search with built-in debounce.

+page.svelte
<!-- src/routes/help/search/+page.svelte -->
<script>
  import { onDestroy } from 'svelte';
  import { getLibraryClient, ArticleSearchState } from '@cstar.help/svelte';

  const client = getLibraryClient();
  const searcher = new ArticleSearchState(client);
  let query = $state('');

  $effect(() => {
    searcher.search(query); // 300ms debounce built in
  });

  onDestroy(() => searcher.destroy());
</script>

<input bind:value={query} placeholder="Search help articles..." />

{#if searcher.isLoading}
  <p>Searching...</p>
{:else}
  <p>{searcher.totalCount} results</p>
{/if}

{#each searcher.results as a (a.id)}
  <a href="/help/{a.slug}">{a.title}</a>
{/each}

What's Next?