Skip to main content

Svelte SDK

@cstar.help/svelte provides Svelte 5 rune classes for customer-facing chat and public knowledge base features. Uses $state and $derived for automatic reactivity — no stores, no boilerplate.

Installation

npm install @cstar.help/svelte

This automatically installs @cstar.help/js (the core client) as a dependency. Requires Svelte 5+ and SvelteKit 2+. For real-time chat, also install @supabase/supabase-js (optional — falls back to polling without it).

Two Provider Architecture

The Svelte SDK has two independent providers — one for chat (authenticated, real-time) and one for the knowledge base (public, no auth needed). Use one or both depending on your needs.

CStarChatProvider

Wrap your chat widget with the chat provider. It creates a ChatClient and makes it available via context. Requires teamSlug.

+layout.svelte
<script>
  import { CStarChatProvider } from '@cstar.help/svelte';

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

<CStarChatProvider
  teamSlug="acme"
  supabaseUrl={import.meta.env.VITE_SUPABASE_URL}
  supabaseAnonKey={import.meta.env.VITE_SUPABASE_ANON_KEY}
>
  {@render children()}
</CStarChatProvider>

Without Supabase config, chat falls back to polling (every 3 seconds). The provider auto-disconnects on component destruction.

Chat State Classes

ChatState

Identify customers and manage the chat session. Must call identify() with an HMAC signature before using other chat classes.

<script>
  import { getChatClient, ChatState } from '@cstar.help/svelte';

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

  async function startChat() {
    await chat.identify({
      externalId: 'user_123',
      email: 'jane@example.com',
      name: 'Jane Doe',
      timestamp: Math.floor(Date.now() / 1000)
    }, 'hmac_signature_from_your_server');
  }
</script>

{#if !chat.isIdentified}
  <button onclick={startChat}>Start Chat</button>
{:else}
  <p>Chat ready! {chat.isRealtimeReady ? '(live)' : '(polling)'}</p>
{/if}

ConversationsState

List and create customer conversations. Auto-fetches on construction.

<script>
  import { getChatClient, ConversationsState } from '@cstar.help/svelte';

  const client = getChatClient();
  const convos = new ConversationsState(client);

  async function startNew() {
    const convo = await convos.create({
      subject: 'Help with billing',
      message: 'I have a question about my invoice.'
    });
    console.log('Created:', convo.id);
  }
</script>

{#if convos.isLoading}
  <p>Loading...</p>
{:else}
  {#each convos.conversations as c (c.id)}
    <div>
      <h3>{c.subject}</h3>
      <span>{c.status} · {c.messageCount} messages</span>
    </div>
  {/each}
  <button onclick={startNew}>New Conversation</button>
{/if}

MessagesState

Send and receive messages with optimistic updates and real-time delivery. Call destroy() on unmount to clean up subscriptions.

<script>
  import { onDestroy } from 'svelte';
  import { getChatClient, MessagesState } from '@cstar.help/svelte';

  let { ticketId } = $props();

  const client = getChatClient();
  const thread = new MessagesState(client, ticketId);
  let draft = $state('');

  async function handleSend() {
    await thread.send(draft); // Optimistic — appears instantly
    draft = '';
  }

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

{#each thread.messages as msg (msg.id)}
  <div>
    <strong>{msg.sender_name}</strong>
    <p>{msg.content}</p>
  </div>
{/each}

<input bind:value={draft} placeholder="Type a message..." />
<button onclick={handleSend}>Send</button>

TypingState

Show agent typing indicators. Auto-clears after 4 seconds of inactivity. Call destroy() on unmount.

<script>
  import { onDestroy } from 'svelte';
  import { getChatClient, TypingState } from '@cstar.help/svelte';

  let { ticketId } = $props();

  const client = getChatClient();
  const typing = new TypingState(client, ticketId);

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

{#if typing.typingAgents.length > 0}
  <p>{typing.typingAgents.map(a => a.agentName).join(', ')} is typing...</p>
{/if}

CStarLibraryProvider

Wrap your help center with the library provider. No authentication needed — the knowledge base is public.

+layout.svelte
<script>
  import { CStarLibraryProvider } from '@cstar.help/svelte';

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

<CStarLibraryProvider teamSlug="acme">
  {@render children()}
</CStarLibraryProvider>

Library State Classes

CategoriesState

<script>
  import { getLibraryClient, CategoriesState } from '@cstar.help/svelte';

  const client = getLibraryClient();
  const cats = new CategoriesState(client);
</script>

{#if cats.isLoading}
  <p>Loading...</p>
{:else}
  {#each cats.categories as cat (cat.id)}
    <a href="/help/{cat.slug}">
      {cat.name} ({cat.article_count} articles)
    </a>
  {/each}
{/if}

ArticlesState

<script>
  import { getLibraryClient, ArticlesState } from '@cstar.help/svelte';

  let { categorySlug } = $props();

  const client = getLibraryClient();
  const list = new ArticlesState(client, { categorySlug, limit: 10 });
</script>

{#if list.isLoading}
  <p>Loading...</p>
{:else}
  {#each list.articles as article (article.id)}
    <a href="/help/articles/{article.slug}">
      <h3>{article.title}</h3>
      <p>{article.excerpt}</p>
    </a>
  {/each}
{/if}

ArticleSearchState

Full-text search with built-in 300ms debounce. Call destroy() on unmount.

<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); // Debounced automatically
  });

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

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

{#if searcher.isLoading}
  <p>Searching...</p>
{/if}

<p>{searcher.totalCount} results</p>

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

SvelteKit SSR

For server-side data loading, use the core @cstar.help/js client with a secret API key in +page.server.js.

+page.server.js
import { CStarClient } from '@cstar.help/js';

export async function load() {
  const cstar = new CStarClient({
    apiKey: import.meta.env.CSTAR_SECRET_KEY,
    teamId: import.meta.env.CSTAR_TEAM_ID
  });

  const { data } = await cstar.articles.list({
    status: 'published',
    category: 'Getting Started'
  });

  return { articles: data };
}

Community State Classes

Wrap in <CStarCommunityProvider> for public community forum access — no auth required. See the Svelte SDK README for full usage examples.

Available Classes

CStarCommunityProvider — Provider component. Pass teamSlug to connect.

TopicsState — Reactive topics list. Properties: topics, isLoading, error. Methods: refresh().

PostsState — Reactive posts list with filters. Properties: posts, count, isLoading, error. Methods: refresh().

PostState — Single post with comments. Properties: data, isLoading, error. Methods: refresh().

CommunitySearchState — Search across posts. Properties: results, isLoading, error. Methods: search(query).

Context Functions

getCommunityClient() — Retrieve the client from the nearest CStarCommunityProvider.

setCommunityClient(client) — Manually set the client in context.

Quick Example

// Inside a child of CStarCommunityProvider
import { TopicsState, PostsState, getCommunityClient } from '@cstar.help/svelte';

const client = getCommunityClient();
const topics = new TopicsState(client);
const posts = new PostsState(client, { sort: 'votes' });

// topics.topics, posts.posts, posts.count are reactive ($state)