Skip to main content

Svelte Quickstart

Build a cStar integration with Svelte in under 5 minutes. This guide covers installation, seeding sample data, provider setup, knowledge base browsing, customer chat, and article search.

1. Install

npm install @cstar.help/svelte

2. Seed Sample Data

If you haven't already, populate your team with sample help center content so you have data to work with:

cstar seed

This creates 5 categories, 12 articles, 3 customers, and 5 tickets. See the quickstart for details.

3. Set Up 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"
    supabaseUrl={import.meta.env.VITE_SUPABASE_URL}
    supabaseAnonKey={import.meta.env.VITE_SUPABASE_ANON_KEY}
  >
    {@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 start conversations and send messages in real-time.

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

  const client = getChatClient();
  const chat = new ChatState(client);
  const convos = new ConversationsState(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 convo = await convos.create({ subject: 'Help needed' });
    activeThread = new MessagesState(client, convo.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?