Skip to main content

Svelte SDK

@cstar.help/svelte ships rune classes — $state and $derived all the way down. Skip the stores, skip the boilerplate. New up a class with your params; the data, loading, and error fields are reactive.

Installation

npm install @cstar.help/svelte

Pulls in @cstar.help/js as a transitive dep. Needs Svelte 5+ and SvelteKit 2+. Real-time messaging is on by default.

Browse by topic

Drilling into something specific? Topics with a dedicated sub-page are linked below; the rest scroll to the matching section on this page.

Setup

Chat classes

Library classes

Recipes

Two providers, two surfaces

Chat needs auth and runs over real-time. Library is public, safe to render statically. Separate provider trees so one tree can't leak state into the other.

CStarChatProvider

Wrap your chat widget with the chat provider. It creates a ChatClient and makes it available via context. Only teamSlug is required — real-time delivery is enabled by default with an automatic polling fallback.

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

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

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

Pass realtime=false to disable the live connection and poll instead (every 3 seconds by default). 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}

TicketsState

List and create the customer's tickets. Auto-fetches on construction.

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

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

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

{#if tix.isLoading}
  <p>Loading...</p>
{:else}
  {#each tix.tickets as t (t.id)}
    <div>
      <h3>{t.title}</h3>
      <span>{t.status} · {t.messageCount} messages</span>
    </div>
  {/each}
  <button onclick={startNew}>New Ticket</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)