# Cossistant documentation URL: /docs Quickstart with your framework [#quickstart-with-your-framework] Start by selecting your framework of choice, then follow the instructions to install the dependencies and structure your app. Cossistant currently ships quickstarts for Next.js and React.
Next.js Next.js Add Cossistant {``} widget to your Next.js project. React React Add Cossistant {``} widget to any React project.
# Visitors URL: /docs/concepts What are Visitors? [#what-are-visitors] **Visitors** are automatically created when someone loads your application with the Cossistant SDK. They represent anonymous users before they're identified as [contacts](/docs/concepts/contacts). Every visitor is unique per device/browser, persisting across page loads and sessions. How Visitors are Tracked [#how-visitors-are-tracked] Cossistant uses a combination of techniques to maintain visitor identity: * **LocalStorage**: Stores a unique visitor ID in the browser * **Fingerprinting**: Browser and device characteristics for additional persistence * **Automatic creation**: No setup required—visitors are created on first load This means a visitor on desktop and the same person on mobile will be two different visitors until they're [identified](/docs/concepts/contacts). Anonymous by Default [#anonymous-by-default] Visitors start anonymous with no personal information: * No name, email, or external ID * Only browser-derived data (language, timezone) * Can start [conversations](/docs/concepts/conversations) without authentication * Perfect for public-facing pages or logged-out users Visitor Properties [#visitor-properties] Each visitor has: * **id**: Unique identifier for this visitor * **language**: Browser language (e.g., "en-US") * **timezone**: Browser timezone (e.g., "America/New\_York") * **isBlocked**: Whether this visitor has been blocked from support * **contact**: The associated contact (null until identified) Identifying Visitors [#identifying-visitors] Transform anonymous visitors into identified [contacts](/docs/concepts/contacts) when users authenticate: Using the Component (Server Components) [#using-the-component-server-components] ```tsx showLineNumbers title="app/dashboard/layout.tsx" import { IdentifySupportVisitor } from "@cossistant/next"; import { auth } from "@/lib/auth"; import { headers } from "next/headers"; export default async function DashboardLayout({ children }) { const session = await auth.api.getSession({ headers: await headers(), }); return (
{session?.user && ( )} {children}
); } ``` Using the Hook (Client Components) [#using-the-hook-client-components] ```tsx showLineNumbers title="components/auth-handler.tsx" "use client"; import { useVisitor } from "@cossistant/next"; import { useEffect } from "react"; export function AuthHandler({ user }) { const { visitor, identify } = useVisitor(); useEffect(() => { if (user && !visitor?.contact) { identify({ externalId: user.id, email: user.email, name: user.name, }); } }, [user, visitor?.contact, identify]); return null; } ``` Once identified, all conversations and data are linked to the [contact](/docs/concepts/contacts), even across different devices. Learn More [#learn-more] * **[Contacts](/docs/concepts/contacts)**: Identified visitors with metadata and cross-device support * **[IdentifySupportVisitor](/docs/support-component#identifying-visitors)**: Component for visitor identification * **[useVisitor](/docs/support-component/hooks#usevisitor)**: Hook for programmatic visitor control # Contacts URL: /docs/concepts/contacts What are Contacts? [#what-are-contacts] **Contacts** are identified [visitors](/docs/concepts). When you identify an anonymous visitor with an `externalId` (your user ID) or `email`, they become a contact. Contacts enable: * **Cross-device support**: Same user on desktop and mobile shares conversation history * **Rich metadata**: Attach context like plan type, MRR, company, or lifecycle stage * **Dashboard visibility**: Support agents see user details alongside conversations * **Persistent identity**: Conversations follow the user, not the device Creating Contacts [#creating-contacts] Identification Requirements [#identification-requirements] A contact requires **at least one** of: * `externalId`: Your internal user ID (recommended) * `email`: User's email address Both are accepted, but `externalId` is preferred for robust cross-system tracking. Using the Component [#using-the-component] ```tsx showLineNumbers title="app/dashboard/layout.tsx" import { IdentifySupportVisitor } from "@cossistant/next"; import { auth } from "@/lib/auth"; import { headers } from "next/headers"; export default async function DashboardLayout({ children }) { const session = await auth.api.getSession({ headers: await headers(), }); return (
{session?.user && ( )} {children}
); } ``` Using the Hook [#using-the-hook] ```tsx showLineNumbers title="components/auth-handler.tsx" "use client"; import { useVisitor } from "@cossistant/next"; import { useEffect } from "react"; export function AuthHandler({ user }) { const { visitor, identify } = useVisitor(); useEffect(() => { if (user && !visitor?.contact) { identify({ externalId: user.id, email: user.email, name: user.name, image: user.avatar, metadata: { plan: user.plan, signupDate: user.createdAt, }, }); } }, [user, visitor?.contact, identify]); return null; } ``` Contact Metadata [#contact-metadata] Metadata provides context to support agents during conversations. It appears in your dashboard alongside chat threads. What to Include [#what-to-include] Common metadata fields: * **plan**: Subscription tier (free, pro, enterprise) * **signupDate**: When the user joined * **company**: Organization name * **mrr**: Monthly recurring revenue * **lifecycleStage**: lead, trial, customer, churned * **lastActive**: Last activity timestamp Metadata Schema [#metadata-schema] ```typescript type VisitorMetadata = Record; ``` Only primitive values are supported—no nested objects or arrays. Updating Metadata [#updating-metadata] Metadata can be updated anytime to reflect user changes: ```tsx showLineNumbers title="components/upgrade-button.tsx" "use client"; import { useVisitor } from "@cossistant/next"; export function UpgradeButton() { const { setVisitorMetadata } = useVisitor(); const handleUpgrade = async () => { await upgradeToPro(); // Update metadata so agents see the new plan await setVisitorMetadata({ plan: "pro", upgradedAt: new Date().toISOString(), mrr: 99, }); }; return ; } ``` Efficient Metadata Updates [#efficient-metadata-updates] Cossistant **hashes metadata** before sending updates. If metadata hasn't changed, no API call is made—preventing unnecessary network requests and database writes. This means you can safely call `setVisitorMetadata()` or re-render `` without performance concerns. One Contact, Multiple Visitors [#one-contact-multiple-visitors] A single contact can have multiple [visitors](/docs/concepts) associated with it: * **Desktop visitor**: User on their laptop * **Mobile visitor**: Same user on their phone * **Tablet visitor**: Same user on their iPad All three visitors share: * Conversation history * Contact metadata * Support context This provides seamless support across devices—agents see the full picture regardless of where the user reaches out. Learn More [#learn-more] * **[Visitors](/docs/concepts)**: Anonymous users before identification * **[IdentifySupportVisitor](/docs/support-component#identifying-visitors)**: Component for creating contacts * **[useVisitor](/docs/support-component/hooks#usevisitor)**: Hook for programmatic contact management * **[Conversations](/docs/concepts/conversations)**: Chat threads associated with contacts # Conversations URL: /docs/concepts/conversations What are Conversations? [#what-are-conversations] **Conversations** are threaded chat sessions between [visitors](/docs/concepts) (or [contacts](/docs/concepts/contacts)) and your support team. Each conversation has a timeline of [items](/docs/concepts/timeline-items)—messages, events, and more. Key Properties [#key-properties] Every conversation includes: * **status**: `open` or `resolved` * **priority**: `low`, `medium`, `high`, or `urgent` * **participants**: Support agents (human or AI) involved * **tags**: Labels for categorization (billing, technical, onboarding, etc.) * **timeline**: Ordered list of [timeline items](/docs/concepts/timeline-items) * **lastTimelineItem**: Most recent activity for sorting/preview Conversation Lifecycle [#conversation-lifecycle] Creation [#creation] Conversations are created when: * A visitor sends their first message through the support widget * A support agent initiates a conversation from the dashboard * Your backend creates one via the API Status Flow [#status-flow] ``` open → resolved ↓ ↑ └───────┘ (can be reopened) ``` * **open**: Active conversation requiring attention * **resolved**: Conversation marked as complete (can be reopened) Priority Levels [#priority-levels] Conversations can be prioritized: * **low**: General questions, non-urgent feedback * **medium**: Standard support requests (default) * **high**: Important issues affecting user experience * **urgent**: Critical problems requiring immediate attention Support agents can adjust priority based on conversation context. Real-time Updates [#real-time-updates] Conversations support **real-time synchronization** via WebSocket: * New messages appear instantly * Typing indicators show when agents are composing * Seen receipts track when messages are read * Status changes broadcast immediately The Cossistant SDK handles all WebSocket management automatically—no configuration needed. Conversation Timeline [#conversation-timeline] Each conversation has a timeline of [items](/docs/concepts/timeline-items): * **Messages**: Text and files from visitors or agents * **Events**: System activities (assigned, resolved, participant joined) * **Future items**: AI tool calls, interactive UI, custom actions The timeline provides a complete audit trail of the conversation. Example Timeline [#example-timeline] ``` 1. [MESSAGE] Visitor: "How do I reset my password?" 2. [EVENT] Agent Sarah joined the conversation 3. [MESSAGE] Sarah: "I can help! Click your profile..." 4. [EVENT] Conversation marked as resolved 5. [MESSAGE] Visitor: "Thanks, that worked!" 6. [EVENT] Conversation reopened ``` Multi-Agent Support [#multi-agent-support] Conversations can involve multiple participants: * **Human agents**: Support team members * **AI agents**: Automated assistants * **Mixed mode**: AI handles initial triage, escalates to humans Agents can: * Join and leave conversations * See full conversation history * Add internal notes (private [timeline items](/docs/concepts/timeline-items)) Tags and Organization [#tags-and-organization] Tag conversations for filtering and reporting: ```typescript tags: ["billing", "urgent", "enterprise-customer"]; ``` Tags help: * Route conversations to specialized teams * Generate reports and analytics * Filter dashboard views * Track common issue types Seen Tracking [#seen-tracking] Cossistant tracks when participants last viewed a conversation: * **Visitors**: Automatic seen updates when widget is open * **Agents**: Tracked in dashboard * **Unread count**: Calculated per participant This powers unread badges in the support widget and dashboard. Cross-Device Continuity [#cross-device-continuity] For identified [contacts](/docs/concepts/contacts), conversations sync across devices: * Start conversation on desktop * Continue on mobile * Same history, same context Anonymous [visitors](/docs/concepts) have device-specific conversations. Learn More [#learn-more] * **[Timeline Items](/docs/concepts/timeline-items)**: Building blocks of conversations * **[Visitors](/docs/concepts)**: Anonymous users who start conversations * **[Contacts](/docs/concepts/contacts)**: Identified users with cross-device history # Timeline Items URL: /docs/concepts/timeline-items What are Timeline Items? [#what-are-timeline-items] **Timeline items** are the building blocks of [conversations](/docs/concepts/conversations). Instead of only supporting messages, Cossistant uses a flexible timeline architecture that supports multiple item types. This design enables rich, auditable conversation histories with: * **Messages**: Text and media from visitors or agents * **Events**: System activities and state changes * **Future expansion**: AI tool calls, interactive UI, custom actions Why Timeline Items? [#why-timeline-items] Traditional chat systems only handle messages. Cossistant's timeline architecture provides: * **Complete audit trail**: See when agents joined, when status changed, who resolved the conversation * **Rich context**: System events provide context between messages * **Extensibility**: New item types can be added without breaking existing conversations * **Flexibility**: Mix messages, events, and future interactive elements in one timeline Current Item Types [#current-item-types] Message Items [#message-items] Text and media content from visitors, agents, or AI. **Structure:** ```typescript { type: "message", parts: [ { type: "text", text: "How do I reset my password?" }, { type: "image", url: "...", mediaType: "image/png" } ], visitorId: "vis_123", // If from visitor userId: null, // If from human agent aiAgentId: null, // If from AI agent visibility: "public", // or "private" for internal notes createdAt: "2024-01-15T10:30:00Z" } ``` **Message Parts:** * **text**: Plain text content * **image**: Image attachments with URL and metadata * **file**: File attachments with URL, name, size Messages can have multiple parts (e.g., text + image). Event Items [#event-items] System-generated activities tracking conversation state changes. **Event Types:** * **assigned**: Agent assigned to conversation * **unassigned**: Agent removed from conversation * **participant\_joined**: Agent joined the conversation * **participant\_left**: Agent left the conversation * **status\_changed**: Conversation status updated (open ↔ resolved) * **priority\_changed**: Priority level adjusted * **tag\_added**: Tag applied to conversation * **tag\_removed**: Tag removed from conversation * **resolved**: Conversation marked as resolved * **reopened**: Resolved conversation reopened * **visitor\_blocked**: Visitor blocked from support * **visitor\_unblocked**: Visitor unblocked **Structure:** ```typescript { type: "event", parts: [ { type: "event", eventType: "participant_joined", actorUserId: "user_456", targetUserId: null, message: "Sarah joined the conversation" } ], visibility: "public", createdAt: "2024-01-15T10:32:00Z" } ``` Events capture **who** did **what** and **when**, creating a transparent history. Future Item Types [#future-item-types] The timeline architecture is designed to support upcoming features: AI Tool Calls [#ai-tool-calls] ```typescript { type: "ai_tool_call", parts: [ { type: "tool_call", tool: "lookup_order", arguments: { orderId: "ord_789" }, result: { status: "shipped", tracking: "1Z..." } } ] } ``` Interactive UI [#interactive-ui] ```typescript { type: "ui_element", parts: [ { type: "action_buttons", options: ["Resolve", "Escalate", "Request More Info"] } ] } ``` Custom Actions [#custom-actions] ```typescript { type: "custom_action", parts: [ { type: "refund_processed", amount: 99, orderId: "ord_789" } ] } ``` Timeline Item Properties [#timeline-item-properties] All timeline items share common fields: * **id**: Unique identifier * **type**: Item type (message, event, etc.) * **parts**: Array of content parts * **conversationId**: Parent conversation * **visibility**: `public` or `private` (internal agent notes) * **createdAt**: Timestamp * **updatedAt**: Last modification timestamp **Actor fields** (who created the item): * **visitorId**: If created by visitor * **userId**: If created by human agent * **aiAgentId**: If created by AI agent Visibility Control [#visibility-control] Timeline items can be: * **public**: Visible to visitors and agents (default) * **private**: Internal agent notes, hidden from visitors This enables agents to: * Add context for other team members * Document resolutions internally * Share insights without visitor visibility Timeline Ordering [#timeline-ordering] Items are ordered chronologically by `createdAt` timestamp, providing a linear conversation history. The most recent item (`lastTimelineItem`) is cached on the conversation for: * Sorting conversations by activity * Displaying conversation previews * Determining unread status Learn More [#learn-more] * **[Conversations](/docs/concepts/conversations)**: Chat threads containing timeline items * **[Visitors](/docs/concepts)**: Anonymous users who create timeline items * **[Contacts](/docs/concepts/contacts)**: Identified users with persistent timelines # Contribute to Cossistant URL: /docs/others/contributors Use this guide if you want to contribute code, docs, or examples to Cossistant. It walks through the local setup, the services that should be running, the parts of the monorepo you will touch most often, and the checks to run before you open a pull request. Prerequisites [#prerequisites] Before you start, make sure your machine has: * **Docker Desktop** - Required for running Postgres and Redis locally * **Bun v1.2+** - Our package manager and runtime ([install Bun](https://bun.sh)) * **Git** - For version control * **Node-compatible shell tooling** - Helpful for local debugging and scripts Quick Start [#quick-start] Clone the monorepo, install dependencies, and start the local stack: ```bash git clone https://github.com/cossistantcom/cossistant.git cd cossistant bun install --workspaces bun dev ``` That's it! The `bun dev` command will: 1. Start Docker Compose (Postgres + Redis containers) 2. Start the API server with Upstash Workflow in local mode 3. Start the Next.js web application **Upstash Workflow runs locally** - No account needed! When the server starts, you'll see the workflow credentials displayed in your console. These credentials don't change between restarts. Local Services At A Glance [#local-services-at-a-glance] After `bun dev` finishes, you should have: * **Web app**: `http://localhost:3000` for the landing site, docs, and dashboard * **API server**: `http://localhost:3001` for REST, tRPC, WebSocket, and auth flows * **Postgres**: `localhost:5432` for relational data * **Redis**: `localhost:6379` for queues, caching, and real-time support * **Upstash Workflow local mode**: credentials printed in the API console output Database Setup [#database-setup] Default connection [#default-connection] The local database connection string is: ``` postgresql://postgres:postgres@localhost:5432/cossistant ``` This is automatically configured when you run `bun dev`. Run migrations [#run-migrations] After starting the services for the first time, run the database migrations: ```bash cd apps/api bun db:migrate ``` Seed data (optional) [#seed-data-optional] To populate the database with sample data for development: ```bash cd apps/api bun db:seed ``` Make schema changes [#make-schema-changes] When you need to modify the database schema: 1. Update the schema files in `apps/api/src/db/schema` 2. Generate a migration: ```bash cd apps/api bun db:generate ``` 3. Apply the migration: ```bash bun db:migrate ``` Open Database Studio [#open-database-studio] To explore the database with Drizzle Studio: ```bash cd apps/api bun db:studio ``` Optional S3 Setup [#optional-s3-setup] S3 file storage is **only needed if you're testing file uploads**. For most development work, you can skip this. If you do need S3: 1. Navigate to the infrastructure directory: ```bash cd infra/aws/s3-public-setup ``` 2. Follow the instructions in the [S3 Setup README](/infra/aws/s3-public-setup/README.md) 3. The Terraform configuration will create the necessary AWS resources Repo Map For Contributors [#repo-map-for-contributors] These are the directories most contributors touch: Apps [#apps] * **`apps/api`** - Hono + tRPC backend with WebSocket server * RESTful and tRPC APIs * Real-time WebSocket communication * Database queries and mutations * Authentication via Better Auth * Background jobs via Upstash Workflow * **`apps/web`** - Next.js application * Marketing landing page * Documentation (Fumadocs) * Dashboard interface Packages [#packages] * **`packages/react`** - Main React SDK * Headless hooks and primitives * Pre-built `` component * Real-time WebSocket integration * **`packages/next`** - Next.js-specific SDK * Server Component support * Next.js optimized bindings * **`packages/core`** - Shared client logic * State management stores * REST and WebSocket clients * Utility functions * **`packages/types`** - TypeScript definitions * Shared types across all packages * API schemas and validation * **`packages/transactional`** - Email templates * React Email templates * Transactional email utilities * **`packages/location`** - Location utilities * Country and timezone data * Geolocation helpers Contributor Workflow [#contributor-workflow] Root commands [#root-commands] Run these from the repo root: ```bash # Start all services bun dev # Build all packages bun run build # Build specific package bun run build --filter @cossistant/react # Run linter and auto-fix issues bun run fix # Type check all packages bun run check-types # Check documentation links bun run docs:links ``` API-specific commands [#api-specific-commands] Run these from `apps/api`: ```bash # Start API server only bun run dev # Run migrations bun run db:migrate # Seed database bun run db:seed # Open Drizzle Studio bun run db:studio # Generate Better Auth schema bun run better-auth:generate-schema ``` Quality Checks Before A Pull Request [#quality-checks-before-a-pull-request] Run the smallest set of checks that matches your change: ```bash # Auto-fix linting issues across the repo bun run fix # Verify TypeScript types bun run check-types # Run docs link checks for documentation edits bun run docs:links # Run tests in the package or app you changed cd packages/react bun test ``` If you changed the API, run tests from `apps/api`. If you changed docs or marketing pages, make sure the affected pages still build and render cleanly. Pull Request Checklist [#pull-request-checklist] Before you open or merge a PR: 1. Use a clear Conventional Commits title such as `fix: tighten llms route indexing headers`. 2. Explain what changed, why it changed, and any follow-up work still pending. 3. Link the related issue, discussion, or support thread when there is one. 4. Add screenshots or recordings for UI changes. 5. Add a changeset when you touch published packages such as `@cossistant/react` or `@cossistant/next`. 6. Mention any environment variables, migrations, or manual QA steps reviewers need. Testing Notes [#testing-notes] Cossistant uses Bun's built-in test runner. Tests are colocated with source files using the `*.test.ts` naming convention. ```bash # Run tests in a specific package cd packages/react bun test # Watch mode bun test --watch # Coverage report bun test --coverage ``` Troubleshooting [#troubleshooting] * **`bun dev` fails immediately**: confirm Docker Desktop is running and ports `3000`, `3001`, `5432`, and `6379` are not already taken. * **Database errors after pulling main**: rerun `bun db:migrate` from `apps/api` to catch up with schema changes. * **Missing dependencies or stale lockfile state**: rerun `bun install --workspaces` from the repo root. * **Docs pages render but links fail**: run `bun run docs:links` before opening the PR. * **You only need package-level tests**: run `bun test` inside the package you changed instead of the entire monorepo. Commit Message Format [#commit-message-format] We follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages: * `feat:` - New features * `fix:` - Bug fixes * `docs:` - Documentation changes * `chore:` - Maintenance tasks * `refactor:` - Code refactoring * `test:` - Test updates Example: `feat: add message reactions to timeline items` Changesets [#changesets] For changes to published packages (`@cossistant/react`, `@cossistant/next`), add a changeset: ```bash bun run changeset ``` Follow the prompts to describe your changes. This will be used to generate changelogs and version bumps. Getting Help [#getting-help] * **Documentation**: [cossistant.com/docs](https://cossistant.com/docs) * **Discord**: [Join our community](https://discord.gg/vQkPjgvzcc) * **GitHub Issues**: [Report bugs or request features](https://github.com/cossistantcom/cossistant/issues) License [#license] Cossistant is licensed under **AGPL-3.0** for non-commercial use. For commercial use, please contact us at [anthony@cossistant.com](mailto:anthony@cossistant.com). # Mentions URL: /docs/others/mentions About [#about] Cossistant is a project by [Anthony Riera](https://x.com/_anthonyriera). Credits [#credits] * [ui.shadcn.com](https://ui.shadcn.com) - For the inspiration, docs, code and components. * [Vercel](https://vercel.com) - Where we host all our projects and for maintaining NextJS & Turborepo. * [Railway](https://railway.com?referralCode=nOTIh8) - Where we host the hono backend. * [Midday](https://midday.ai) - For the initial inspiration, motivation to go open source and code structure. * [Anron Icons](https://anron.pro) - For the icons (we paid for the license ofc). License [#license] © 2025 [Cossistant](https://cossistant.com). open source under GPL-3.0 license. # Third-Party Services URL: /docs/others/third-party-services Overview [#overview] Cossistant leverages best-in-class third-party services to deliver a secure, reliable, and performant customer support platform. We are committed to achieving SOC 2 Type II certification as soon as possible and have carefully selected service providers that maintain the highest security and compliance standards. Infrastructure & Hosting [#infrastructure--hosting] * **[Vercel](https://vercel.com)** - Hosts our Next.js web application and provides edge infrastructure for optimal performance worldwide. * **[Railway](https://railway.com)** - Hosts our Hono backend API, Redis instance, and provides automatic deployments with monitoring. * **[AWS S3](https://aws.amazon.com/s3/)** - Secure cloud storage for file uploads and media assets (SOC 2 Type II certified). * **[AWS CloudFront](https://aws.amazon.com/cloudfront/)** - Content delivery network (CDN) for fast, global content distribution (SOC 2 Type II certified). * **[Upstash](https://upstash.com)** - QStash and Workflows for serverless background job processing and workflow orchestration (SOC 2 Type II certified). Database & Storage [#database--storage] * **[PostgreSQL](https://www.postgresql.org/)** - Primary database for all application data. * **[Drizzle ORM](https://orm.drizzle.team/)** - Type-safe database toolkit and ORM. Authentication & Payments [#authentication--payments] * **[Better Auth](https://www.better-auth.com/)** - Modern authentication solution for secure user authentication. * **[Polar.sh](https://polar.sh)** - Payment processing and subscription management for our billing system. Communication [#communication] * **[Resend](https://resend.com)** - Transactional email delivery with high deliverability rates (SOC 2 Type II certified). Monitoring & Analytics [#monitoring--analytics] * **[Vercel Analytics](https://vercel.com/analytics)** - Privacy-friendly web analytics and performance monitoring. * **[OpenStatus](https://www.openstatus.dev/)** - Uptime monitoring and status page infrastructure. * **[Tinybird](https://www.tinybird.co/)** - Real-time analytics platform for inbox metrics, visitor tracking, and geolocation data (SOC 2 Type II certified). Security & Compliance Commitment [#security--compliance-commitment] All third-party services we use are carefully vetted for: * SOC 2 Type II compliance (current or in progress) * GDPR compliance * Robust data encryption (in transit and at rest) * Regular security audits and penetration testing * High availability and disaster recovery capabilities We regularly review our vendor landscape to ensure we maintain the highest standards of security and privacy for our customers. # Next.js URL: /docs/quickstart If you are contributing to the Cossistant repo itself, start with the [Contributor Setup Guide](/docs/others/contributors) so you boot the full monorepo and local services, not just the widget in an existing app. Quick start with AI prompt [#quick-start-with-ai-prompt] Manually [#manually] 1\. Install the package [#1-install-the-package] ```bash npm install @cossistant/next ``` 2\. Add your public API key [#2-add-your-public-api-key] ```bash title=".env.local" NEXT_PUBLIC_COSSISTANT_API_KEY=pk_test_xxxx ``` 3\. Add `SupportProvider` [#3-add-supportprovider] ```tsx title="app/layout.tsx" import { SupportProvider } from "@cossistant/next"; import "./globals.css"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` 4\. Import styles [#4-import-styles] Tailwind v4 Plain CSS ```css title="app/globals.css" @import "tailwindcss"; @import "@cossistant/next/support.css"; ``` ```tsx title="app/layout.tsx" import { SupportProvider } from "@cossistant/next"; import "@cossistant/next/styles.css"; import "./globals.css"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` 5\. Render the widget [#5-render-the-widget] ```tsx title="app/page.tsx" import { Support } from "@cossistant/next"; export default function Page() { return (

You are ready to chat

); } ``` 6\. Identify logged-in visitors (optional) [#6-identify-logged-in-visitors-optional] ```tsx title="app/(app)/layout.tsx" import { IdentifySupportVisitor } from "@cossistant/next"; export default function AppLayout({ children }: { children: React.ReactNode }) { const user = { id: "user_123", email: "jane@acme.com", name: "Jane Doe", }; return ( <> {children} ); } ``` 7\. Display custom messages with `SupportConfig defaultMessages` [#7-display-custom-messages-with-supportconfig-defaultmessages] ```tsx title="app/page.tsx" import { Support, SupportConfig } from "@cossistant/next"; import { type DefaultMessage, SenderType } from "@cossistant/types"; const user: { name: string | null } = { name: "Jane Doe", }; const defaultMessages: DefaultMessage[] = [ { content: `Hi ${user.name ?? "there"}, anything I can help with?`, senderType: SenderType.TEAM_MEMBER, }, ]; const quickOptions: string[] = ["How to identify a visitor?"]; export default function Page() { return ( <> ); } ``` You can now customize behavior in the [Support component guide](/docs/support-component). # API Keys URL: /docs/quickstart/api-keys Get your public key [#get-your-public-key] Open your dashboard at **Settings → Developers** and create or copy a **Public key**. Configure environment variables [#configure-environment-variables] Next.js Vite Other ```bash title=".env" VITE_COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx ``` ```bash title=".env.local" NEXT_PUBLIC_COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx ``` ```bash title=".env" COSSISTANT_API_KEY=pk_live_xxxxxxxxxxxx ``` Auto-detection The SDK automatically detects your framework and checks `VITE_COSSISTANT_API_KEY` (Vite), `NEXT_PUBLIC_COSSISTANT_API_KEY` (Next.js), or `COSSISTANT_API_KEY` (other). You can also pass the key explicitly through `publicKey`. Public vs private keys [#public-vs-private-keys] | Type | Prefix | Purpose | Safe in browser | | ----------- | ------------------------ | -------------------- | --------------- | | **Public** | `pk_live_*`, `pk_test_*` | Widget integration | Yes | | **Private** | `sk_live_*`, `sk_test_*` | Server-to-server API | No | Only use **public** keys in frontend widget code. Allowed domains [#allowed-domains] Public keys work only on whitelisted domains. * Production keys (`pk_live_*`) require explicit allowlisting. * Test keys (`pk_test_*`) work on localhost. Use full origins, for example: * `https://example.com` * `https://app.example.com` * `https://staging.example.com` Troubleshooting [#troubleshooting] Configuration error in the widget [#configuration-error-in-the-widget] Check: * env variable name is correct for your framework * key is still active * app was restarted after env changes Domain not allowed [#domain-not-allowed] Check: * domain is listed in **Settings → Developers → Allowed domains** * protocol matches (`https://`) # React URL: /docs/quickstart/react Quick start with AI prompt [#quick-start-with-ai-prompt] Manually [#manually] 1\. Install the package [#1-install-the-package] ```bash npm install @cossistant/react ``` 2\. Add your public API key [#2-add-your-public-api-key] Vite Next.js Other ```bash title=".env" VITE_COSSISTANT_API_KEY=pk_test_xxxx ``` ```bash title=".env.local" NEXT_PUBLIC_COSSISTANT_API_KEY=pk_test_xxxx ``` For other frameworks (CRA, Remix, etc.), either set the env variable: ```bash title=".env" COSSISTANT_API_KEY=pk_test_xxxx ``` Or pass the key directly via the `publicKey` prop: ```tsx ``` Auto-detection The SDK automatically detects your framework and reads the right environment variable (`VITE_COSSISTANT_API_KEY`, `NEXT_PUBLIC_COSSISTANT_API_KEY`, or `COSSISTANT_API_KEY`). You can also pass the key explicitly through `publicKey`. 3\. Add `SupportProvider` [#3-add-supportprovider] ```tsx title="src/main.tsx" import React from "react"; import ReactDOM from "react-dom/client"; import { SupportProvider } from "@cossistant/react"; import App from "./App"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` Passing the key explicitly If your framework does not support automatic env variable detection, pass `publicKey` directly: ``. 4\. Import styles [#4-import-styles] Tailwind v4 Plain CSS ```css title="src/index.css" @import "tailwindcss"; @import "@cossistant/react/support.css"; ``` ```tsx title="src/main.tsx" import React from "react"; import ReactDOM from "react-dom/client"; import { SupportProvider } from "@cossistant/react"; import "@cossistant/react/styles.css"; import App from "./App"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` 5\. Render the widget [#5-render-the-widget] ```tsx title="src/App.tsx" import { Support } from "@cossistant/react"; export default function App() { return (

You are ready to chat

); } ``` 6\. Identify logged-in visitors (optional) [#6-identify-logged-in-visitors-optional] ```tsx title="src/App.tsx" import { IdentifySupportVisitor, Support } from "@cossistant/react"; export default function App() { const user = { id: "user_123", email: "jane@acme.com", name: "Jane Doe", }; return ( <> ); } ``` 7\. Display custom messages with `SupportConfig defaultMessages` [#7-display-custom-messages-with-supportconfig-defaultmessages] ```tsx title="src/App.tsx" import { Support, SupportConfig } from "@cossistant/react"; import { type DefaultMessage, SenderType } from "@cossistant/types"; const user: { name: string | null } = { name: "Jane Doe", }; const defaultMessages: DefaultMessage[] = [ { content: `Hi ${user.name ?? "there"}, anything I can help with?`, senderType: SenderType.TEAM_MEMBER, }, ]; const quickOptions: string[] = ["How to identify a visitor?"]; export default function App() { return ( <> ); } ``` You can now customize behavior in the [Support component guide](/docs/support-component). # Basic usage URL: /docs/support-component Philosophy [#philosophy] The `` component is built on **progressive disclosure**. Start with zero config, then customize as you grow. How it Works [#how-it-works] * **Zero Config**: Works out of the box with sensible defaults * **Progressive Enhancement**: Start simple, customize as needed * **Compound Components**: Radix-style API for full control when needed * **Real-time Chat**: Built-in WebSocket support for live conversations * **Headless Architecture**: Full control through primitive components when needed Quick Start [#quick-start] Drop `` anywhere in your app: ```tsx import { Support } from "@cossistant/next"; export default function App() { return (
{/* Your app */}
); } ``` That's it. You get a fully functional support widget. Customizing Messages & Quick Options [#customizing-messages--quick-options] Use `` to customize messages and quick options per page: ```tsx title="app/pricing/page.tsx" import { SupportConfig } from "@cossistant/next"; import { type DefaultMessage, SenderType } from "@cossistant/types"; const user: { name: string | null } = { name: "Jane Doe", }; const defaultMessages: DefaultMessage[] = [ { content: `Hi ${user.name ?? "there"}, anything I can help with?`, senderType: SenderType.TEAM_MEMBER, }, ]; const quickOptions: string[] = [ "How does billing work?", "What's included in Pro?", "Can I cancel anytime?", ]; export default function PricingPage() { return ( <>

Choose your plan

); } ``` Identifying Visitors [#identifying-visitors] By default, visitors are **anonymous**. Use `` to connect them to your authenticated users: ```tsx title="app/dashboard/layout.tsx" import { IdentifySupportVisitor } from "@cossistant/next"; import { auth } from "@/lib/auth"; import { headers } from "next/headers"; export default async function DashboardLayout({ children, }: { children: React.ReactNode; }) { const session = await auth.api.getSession({ headers: await headers(), }); return (
{session?.user && ( )} {children}
); } ``` What happens when I identify a visitor? The anonymous visitor becomes associated with a contact. All their conversations, metadata, and future interactions are linked to this contact—even across different devices. Learn more about{" "} visitors and{" "} contacts. Props [#props] Support Component Props [#support-component-props] Usage Examples [#usage-examples] Basic styling: ```tsx ``` With positioning: ```tsx ``` With custom page: ```tsx import { Support, useSupportNavigation } from "@cossistant/next"; const HelpPage = () => { const { goBack } = useSupportNavigation(); return (

Help Center

); }; export default function App() { return ( ); } ``` For advanced customization, see the [Customization](/docs/support-component/customization) guide. Types [#types] Shared support widget types now live on the [Types](/docs/support-component/types) page. Use that reference for `DefaultMessage`, `VisitorMetadata`, `SenderType`, `TriggerRenderProps`, and the rest of the canonical support type docs. # Customization URL: /docs/support-component/customization Overview [#overview] The Support widget follows **progressive disclosure**. Start with zero config, add styling, then compose your own components when needed. Level 1: Style Overrides [#level-1-style-overrides] Override styles with `classNames`: ```tsx ``` Available classNames [#available-classnames] * **`trigger`** - The floating chat button * **`content`** - The main widget dialog Level 2: Positioning [#level-2-positioning] Control where the content appears relative to the trigger: ```tsx ``` Auto Collision Avoidance [#auto-collision-avoidance] The widget automatically repositions itself to stay within the viewport. When there isn't enough space on the preferred side, it flips to the opposite side. It also shifts along the axis to prevent clipping at screen edges. ```tsx // Collision avoidance is enabled by default // Disable collision avoidance for static positioning // Custom padding from viewport edges (default: 8px) // Per-side collision padding ``` Level 3: Custom Trigger [#level-3-custom-trigger] Replace the default trigger with ``: ```tsx import { Support, type TriggerRenderProps } from "@cossistant/next"; {({ isOpen, unreadCount }: TriggerRenderProps) => ( <> {isOpen ? "Close" : "Help"} {unreadCount > 0 && ( {unreadCount} )} )} ``` Trigger Render Props [#trigger-render-props] | Prop | Type | Description | | ------------- | ------------ | ------------------------------------- | | `isOpen` | `boolean` | Whether the widget is currently open | | `unreadCount` | `number` | Number of unread messages | | `isTyping` | `boolean` | Whether an agent is typing | | `toggle` | `() => void` | Function to toggle widget open/closed | Level 4: Custom Content [#level-4-custom-content] Control the content positioning and styling with ``: ```tsx {(props) => } ``` Level 5: Full Composition [#level-5-full-composition] For complete control, use ``: ```tsx import { Support } from "@cossistant/next"; export default function App() { return ( ); } ``` Custom Trigger in a Topbar [#custom-trigger-in-a-topbar] A common use case is placing the trigger in your navigation bar: ```tsx import { Support, type TriggerRenderProps } from "@cossistant/next"; function Topbar() { return (
); } ``` Custom Pages [#custom-pages] Add custom pages to the router: ```tsx import { Support, useSupportNavigation } from "@cossistant/next"; const FAQPage = () => { const { goBack } = useSupportNavigation(); return (

Frequently Asked Questions

); }; ``` Or with the router directly: ```tsx ``` Type Reference [#type-reference] TriggerRenderProps [#triggerrenderprops] ```tsx type TriggerRenderProps = { isOpen: boolean; isTyping: boolean; unreadCount: number; toggle: () => void; }; ``` ContentProps [#contentprops] ```tsx type CollisionPadding = | number | { top?: number; right?: number; bottom?: number; left?: number }; type ContentProps = { className?: string; side?: "top" | "bottom" | "left" | "right"; align?: "start" | "center" | "end"; sideOffset?: number; avoidCollisions?: boolean; // default: true collisionPadding?: CollisionPadding; // default: 8 children?: React.ReactNode; }; ``` SupportProps [#supportprops] ```tsx type SupportProps = { className?: string; side?: "top" | "bottom" | "left" | "right"; align?: "start" | "center" | "end"; sideOffset?: number; avoidCollisions?: boolean; // default: true collisionPadding?: CollisionPadding; // default: 8 classNames?: { trigger?: string; content?: string; }; theme?: "light" | "dark"; defaultOpen?: boolean; locale?: string; content?: SupportTextContentOverrides; quickOptions?: string[]; defaultMessages?: DefaultMessage[]; customPages?: CustomPage[]; children?: React.ReactNode; }; ``` Full Example [#full-example] Combining multiple customizations: ```tsx import { Support, type TriggerRenderProps } from "@cossistant/next"; import { MessageCircle, X } from "lucide-react"; const TriggerContent = ({ isOpen, unreadCount }: TriggerRenderProps) => ( <> {isOpen ? : } {unreadCount > 0 && ( {unreadCount} )} ); export default function App() { return ( {(props) => } ); } ``` # Events URL: /docs/support-component/events Overview [#overview] The Support widget emits events throughout its lifecycle, allowing you to: * Track user interactions for analytics * Log events for debugging * Trigger custom behavior based on widget activity * Integrate with third-party services Event Callback Props [#event-callback-props] The simplest way to listen to events is via callback props on the `` component: ```tsx showLineNumbers title="app/layout.tsx" "use client"; import { Support } from "@cossistant/next"; export function SupportWidget() { return ( { analytics.track("support_conversation_started", { conversationId }); }} onConversationEnd={({ conversationId }) => { analytics.track("support_conversation_ended", { conversationId }); }} onMessageSent={({ conversationId, message }) => { analytics.track("support_message_sent", { conversationId, messageId: message.id, }); }} onMessageReceived={({ conversationId, message }) => { analytics.track("support_message_received", { conversationId, messageId: message.id, }); }} onError={({ error, context }) => { logger.error("Support widget error", { error, context }); }} /> ); } ``` Available Events [#available-events] onConversationStart [#onconversationstart] Fired when a new conversation is created. onConversationEnd [#onconversationend] Fired when a conversation is closed or resolved. onMessageSent [#onmessagesent] Fired when the visitor sends a message. onMessageReceived [#onmessagereceived] Fired when a message is received from an agent (human or AI). onError [#onerror] Fired when an error occurs within the widget. Programmatic Event Handling [#programmatic-event-handling] For more control, use the `useSupportEvents` hook to subscribe to events from any component within the widget: ```tsx showLineNumbers title="components/analytics-tracker.tsx" "use client"; import { useSupportEvents } from "@cossistant/next/support"; import { useEffect } from "react"; export function AnalyticsTracker() { const events = useSupportEvents(); useEffect(() => { if (!events) return; // Subscribe to multiple event types const unsubscribes = [ events.subscribe("conversationStart", (event) => { analytics.track("conversation_started", event); }), events.subscribe("messageSent", (event) => { analytics.track("message_sent", event); }), events.subscribe("messageReceived", (event) => { analytics.track("message_received", event); }), events.subscribe("error", (event) => { errorTracker.captureException(event.error, { extra: { context: event.context }, }); }), ]; // Cleanup all subscriptions return () => { unsubscribes.forEach((unsubscribe) => unsubscribe()); }; }, [events]); return null; } ``` Where to place the tracker The `AnalyticsTracker` component should be rendered inside the Support widget tree or within a component wrapped by `SupportProvider`. The events context is scoped to the widget instance. Emitting Custom Events [#emitting-custom-events] From within custom pages or components, use `useSupportEventEmitter` to emit events: ```tsx showLineNumbers title="pages/custom-conversation.tsx" "use client"; import { useSupportEventEmitter } from "@cossistant/next/support"; export function CustomConversationPage({ conversationId }: { conversationId: string }) { const emitter = useSupportEventEmitter(); const handleSendMessage = async (message: string) => { try { const sentMessage = await sendMessage(conversationId, message); // Emit the event for other listeners emitter.emitMessageSent(conversationId, sentMessage); } catch (error) { emitter.emitError(error as Error, "message_send_failed"); } }; return ( // ...your component ); } ``` Integration Examples [#integration-examples] Google Analytics 4 [#google-analytics-4] ```tsx showLineNumbers title="components/ga-tracker.tsx" "use client"; import { Support } from "@cossistant/next"; export function SupportWithGA() { return ( { gtag("event", "support_conversation_start", { conversation_id: conversationId, }); }} onMessageSent={({ conversationId, message }) => { gtag("event", "support_message_sent", { conversation_id: conversationId, message_id: message.id, }); }} /> ); } ``` Sentry Error Tracking [#sentry-error-tracking] ```tsx showLineNumbers title="components/sentry-support.tsx" "use client"; import { Support } from "@cossistant/next"; import * as Sentry from "@sentry/react"; export function SupportWithSentry() { return ( { Sentry.captureException(error, { tags: { component: "support_widget", }, extra: { context, }, }); }} /> ); } ``` Segment [#segment] ```tsx showLineNumbers title="components/segment-support.tsx" "use client"; import { Support } from "@cossistant/next"; import { analytics } from "./analytics"; export function SupportWithSegment() { return ( { analytics.track("Support Conversation Started", { conversationId, }); }} onConversationEnd={({ conversationId }) => { analytics.track("Support Conversation Ended", { conversationId, }); }} onMessageSent={({ conversationId, message }) => { analytics.track("Support Message Sent", { conversationId, messageId: message.id, hasAttachments: message.parts.some( (part) => part.type === "image" || part.type === "file" ), }); }} /> ); } ``` PostHog [#posthog] ```tsx showLineNumbers title="components/posthog-support.tsx" "use client"; import { Support } from "@cossistant/next"; import { usePostHog } from "posthog-js/react"; export function SupportWithPostHog() { const posthog = usePostHog(); return ( { posthog.capture("support_conversation_started", { conversationId, }); }} onMessageSent={({ conversationId }) => { posthog.capture("support_message_sent", { conversationId, }); }} /> ); } ``` Event Types [#event-types] All event types are exported for TypeScript usage: ```tsx import type { SupportEvent, SupportEventType, ConversationStartEvent, ConversationEndEvent, MessageSentEvent, MessageReceivedEvent, ErrorEvent, SupportEventCallbacks, } from "@cossistant/next/support"; ``` # Hooks URL: /docs/support-component/hooks Overview [#overview] Cossistant provides React hooks for programmatic control over every aspect of the support widget. These hooks follow a layered architecture: * **Core hooks** (`useSupport`, `useVisitor`) - Primary integration points * **Widget state hooks** (`useSupportConfig`, `useSupportNavigation`) - Control widget behavior * **Page hooks** (`useHomePage`, `useConversationPage`) - Build custom pages * **Utility hooks** (`useFileUpload`, `useMessageComposer`) - Compose functionality useSupport [#usesupport] Access support widget state and controls from any client component. Basic Example [#basic-example] ```tsx showLineNumbers title="components/custom-support-button.tsx" "use client"; import { useSupport } from "@cossistant/next"; export function CustomSupportButton() { const { isOpen, toggle, unreadCount } = useSupport(); return ( ); } ``` Return Values [#return-values] useVisitor [#usevisitor] Programmatically identify visitors and manage contact metadata. Important: Metadata storage Metadata is stored on **contacts**, not visitors. You must call `identify()` before `setVisitorMetadata()` will work. Learn more about{" "} visitors and{" "} contacts. Example: Identify on Auth [#example-identify-on-auth] ```tsx showLineNumbers title="components/auth-handler.tsx" "use client"; import { useVisitor } from "@cossistant/next"; import { useEffect } from "react"; export function AuthHandler({ user }) { const { visitor, identify } = useVisitor(); useEffect(() => { // Only identify if we have a user and visitor isn't already a contact if (user && !visitor?.contact) { identify({ externalId: user.id, email: user.email, name: user.name, image: user.avatar, }); } }, [user, visitor?.contact, identify]); return null; } ``` Example: Update Metadata on Action [#example-update-metadata-on-action] ```tsx showLineNumbers title="components/upgrade-button.tsx" "use client"; import { useVisitor } from "@cossistant/next"; export function UpgradeButton() { const { setVisitorMetadata } = useVisitor(); const handleUpgrade = async () => { // Upgrade user's plan await upgradeToPro(); // Update contact metadata so support agents see the change await setVisitorMetadata({ plan: "pro", upgradedAt: new Date().toISOString(), mrr: 99, }); }; return ; } ``` Return Values [#return-values-1] identify() Parameters [#identify-parameters] Prefer declarative code? Use the{" "} IdentifySupportVisitor {" "} component for a simpler, declarative approach to visitor identification in Server Components. useSupportConfig [#usesupportconfig] Access and control widget visibility and size configuration. Basic Example [#basic-example-1] ```tsx showLineNumbers title="components/custom-toggle.tsx" "use client"; import { useSupportConfig } from "@cossistant/next/support"; export function CustomToggle() { const { isOpen, open, close, toggle, size } = useSupportConfig(); return (
Size: {size}
); } ``` Return Values [#return-values-2] Controlled mode support When using controlled mode (`open` and `onOpenChange` props on Support), these functions will call `onOpenChange` instead of modifying internal state. useSupportNavigation [#usesupportnavigation] Access navigation state and routing methods for the widget. Basic Example [#basic-example-2] ```tsx showLineNumbers title="components/navigation-buttons.tsx" "use client"; import { useSupportNavigation } from "@cossistant/next/support"; export function NavigationButtons() { const { page, navigate, goBack, canGoBack } = useSupportNavigation(); return (
{canGoBack && } Current page: {page}
); } ``` Return Values [#return-values-3] useSupportHandle [#usesupporthandle] Access the imperative handle from within the widget tree. Alternative to using refs on the Support component. The hook returns `null` outside the widget tree, and the table below describes the handle when it is available. Basic Example [#basic-example-3] ```tsx showLineNumbers title="components/help-button.tsx" "use client"; import { useSupportHandle } from "@cossistant/next/support"; export function HelpButton() { const support = useSupportHandle(); const handleNeedHelp = () => { // Open support and start a new conversation support?.startConversation("I need help with my order"); }; return ( ); } ``` Return Values [#return-values-4] useHomePage [#usehomepage] Logic hook for building custom home pages. Provides all state and actions needed for the home page. Basic Example [#basic-example-4] ```tsx showLineNumbers title="pages/custom-home.tsx" "use client"; import { useHomePage } from "@cossistant/next"; export function CustomHomePage() { const home = useHomePage({ onStartConversation: () => console.log("Conversation started"), onOpenConversation: (id) => console.log("Opened:", id), }); return (

Welcome!

{home.lastOpenConversation && ( )} {home.availableConversationsCount > 0 && ( )}
); } ``` Return Values [#return-values-5] useConversationPage [#useconversationpage] Logic hook for building custom conversation pages. Manages the conversation lifecycle, messages, and composer. Basic Example [#basic-example-5] ```tsx showLineNumbers title="pages/custom-conversation.tsx" "use client"; import { useConversationPage } from "@cossistant/next"; export function CustomConversationPage({ conversationId }: { conversationId: string }) { const conversation = useConversationPage({ conversationId, onConversationIdChange: (id) => console.log("Active:", id), }); return (
{/* Messages */}
{conversation.items.map((item) => (
{/* Render message */}
))}
{/* Composer */}
{ e.preventDefault(); conversation.composer.submit(); }}> conversation.composer.setMessage(e.target.value)} placeholder={conversation.isPending ? "Start the conversation..." : "Type a message..."} />
); } ``` Return Values [#return-values-6] useMessageComposer [#usemessagecomposer] Hook for managing message composition with file attachments. Basic Example [#basic-example-6] ```tsx showLineNumbers title="components/message-input.tsx" "use client"; import { useMessageComposer } from "@cossistant/next"; export function MessageInput({ conversationId }: { conversationId: string }) { const composer = useMessageComposer({ conversationId, onMessageSent: () => console.log("Message sent!"), }); return (
{ e.preventDefault(); composer.submit(); }}> composer.setMessage(e.target.value)} placeholder="Type a message..." /> { if (e.target.files) { composer.addFiles(Array.from(e.target.files)); } }} /> {composer.files.map((file, index) => ( {file.name}{" "} ))}
); } ``` Return Values [#return-values-7] useFileUpload [#usefileupload] Hook for handling file uploads with progress tracking. Basic Example [#basic-example-7] ```tsx showLineNumbers title="components/file-uploader.tsx" "use client"; import { useFileUpload } from "@cossistant/next"; export function FileUploader() { const upload = useFileUpload(); const conversationId = "conv_123"; return (
{ if (e.target.files?.length) { await upload.uploadFiles(Array.from(e.target.files), conversationId); } }} /> {upload.isUploading && (
{upload.progress}%
)} {upload.error &&

{upload.error.message}

}
); } ``` Return Values [#return-values-8] useSupportText [#usesupporttext] Access the localization system for the support widget. Basic Example [#basic-example-8] ```tsx showLineNumbers title="components/localized-button.tsx" "use client"; import { useSupportText } from "@cossistant/next/support"; export function LocalizedButton() { const format = useSupportText(); return ( ); } ``` Returned Formatter [#returned-formatter] `useSupportText()` returns a formatter function. The table below documents that function reference. useSupportEvents [#usesupportevents] Access the events context for subscribing to widget lifecycle events. The hook returns `null` when used outside the widget's event provider, and the table below documents the event context when present. Basic Example [#basic-example-9] ```tsx showLineNumbers title="components/analytics-tracker.tsx" "use client"; import { useSupportEvents } from "@cossistant/next/support"; import { useEffect } from "react"; export function AnalyticsTracker() { const events = useSupportEvents(); useEffect(() => { if (!events) return; const unsubscribe = events.subscribe("messageSent", (event) => { // Track in your analytics analytics.track("support_message_sent", { conversationId: event.conversationId, }); }); return unsubscribe; }, [events]); return null; } ``` Return Values [#return-values-9] useSupportEventEmitter [#usesupporteventemitter] Convenience hook for emitting events from within the widget. Return Values [#return-values-10] Types [#types] Shared support hook and data-model types now live on the [Types](/docs/support-component/types) page. Use it for `PublicVisitor`, `PublicWebsiteResponse`, `CossistantClient`, `TimelineItem`, `Conversation`, and the rest of the canonical support type reference. # Primitives URL: /docs/support-component/primitives Philosophy [#philosophy] Primitives are **headless UI components** that give you complete control over styling and behavior. Inspired by **shadcn/ui**, they follow these principles: * **Unstyled by default** - Bring your own Tailwind classes * **Fully composable** - Build complex UIs from simple pieces * **Developer-owned** - Copy, modify, and extend as needed * **Accessible** - Built-in ARIA patterns and keyboard navigation Use primitives when you need **complete control** over the support experience. Import [#import] Access primitives via the `Primitives` namespace: ```tsx import { Primitives } from "@cossistant/next"; // Use individual primitives ... ... ... ``` Or import the namespace with an alias: ```tsx import { Primitives as P } from "@cossistant/next"; ... ``` Quick Example [#quick-example] ```tsx import { Primitives, useSupportConfig } from "@cossistant/next"; function CustomWidget() { const { isOpen, toggle } = useSupportConfig(); return ( <> {({ toggle, unreadCount }) => ( )} {({ isOpen, close }) => ( isOpen && (

Custom support content

) )}
); } ``` Primitives Reference [#primitives-reference] Core Components [#core-components] **``** Trigger button with widget state. Can be placed anywhere in the DOM. ```tsx {({ isOpen, unreadCount, isTyping, toggle }) => ( )} ``` **``** Dialog container with open/close state and escape key handling. ```tsx {({ isOpen, close }) => ( isOpen && (

Window content

) )}
``` Timeline & Messages [#timeline--messages] **``** Message timeline container with automatic scrolling and loading states. ```tsx {messages.map(msg => ( ))} ``` **``** Individual message or event in the timeline. ```tsx {message.text} {message.createdAt} ``` **``** Group consecutive messages by the same sender. ```tsx {user.name} {messages.map(msg => ( ))} ``` Input & Interaction [#input--interaction] **``** Rich text input with file upload support. ```tsx ``` **``** File upload component with drag-and-drop. ```tsx ``` **` )} {/* Custom window */} {({ close }) => ( isOpen && (

Support

Your custom support content here

) )}
); } // Wrap with provider export function App() { return ( ); } ``` Why Primitives? [#why-primitives] **Full control.** No opinionated styles or structure—build exactly what you need. **Type-safe.** All primitives are fully typed with TypeScript. **Framework-agnostic patterns.** Works in any React environment. Use the `` component for quick setup. Use primitives when you need **complete freedom**. # Routing & Pages URL: /docs/support-component/routing Overview [#overview] Add custom pages to the Support widget using **declarative `` components**. All navigation is **strongly typed** with full autocomplete for route names and parameters. Adding a Custom Page [#adding-a-custom-page] Step 1: Define route types [#step-1-define-route-types] Type augmentation enables **full TypeScript autocomplete** for your custom routes: ```tsx declare module "@cossistant/core" { interface RouteRegistry { SETTINGS: { tab: string }; HELP: undefined; } } ``` Step 2: Create your page component [#step-2-create-your-page-component] ```tsx import { useSupportNavigation, Header, Button } from "@cossistant/next"; const SettingsPage = ({ params }) => { const { navigate, goBack, canGoBack } = useSupportNavigation(); return ( <>
{canGoBack && }

Settings

Current tab: {params?.tab}

); }; ``` Step 3: Register with `` [#step-3-register-with-supportpage] ```tsx import { Support } from "@cossistant/next"; ``` Step 4: Navigate [#step-4-navigate] ```tsx const { navigate } = useSupportNavigation(); // ✅ Fully typed with autocomplete navigate({ page: "SETTINGS", params: { tab: "profile" } }); ``` Navigation Hooks [#navigation-hooks] The `useSupportNavigation` hook provides everything needed for routing: ```tsx import { useSupportNavigation } from "@cossistant/next"; const MyPage = () => { const { navigate, // Navigate to a page (adds to history) replace, // Replace current page (no history) goBack, // Go to previous page canGoBack, // Can navigate back? current, // Current navigation state page, // Current page name params, // Current page params } = useSupportNavigation(); return (
{canGoBack && }

Page: {page}

); }; ``` Navigation Methods [#navigation-methods] | Method | Description | Example | | ------------ | ---------------------------------- | ------------------------------------------------------------ | | `navigate()` | Go to a new page (adds to history) | `navigate({ page: "SETTINGS", params: { tab: "profile" } })` | | `replace()` | Replace current page (no history) | `replace({ page: "HOME" })` | | `goBack()` | Return to previous page | `goBack()` | Built-in Pages [#built-in-pages] The widget includes four default pages: ```tsx // Home page navigate({ page: "HOME" }); // Articles/FAQ page navigate({ page: "ARTICLES" }); // Conversation page navigate({ page: "CONVERSATION", params: { conversationId: "abc123", initialMessage: "Hello!" } }); // Conversation history navigate({ page: "CONVERSATION_HISTORY" }); ``` Type Safety [#type-safety] TypeScript validates **both route names and params**: ```tsx // ✅ Valid - conversationId is required navigate({ page: "CONVERSATION", params: { conversationId: "123" } }); // ❌ TypeScript error - missing required param navigate({ page: "CONVERSATION", params: {} }); // ✅ Custom routes work too navigate({ page: "SETTINGS", params: { tab: "profile" } }); // ❌ TypeScript error - invalid page name navigate({ page: "UNKNOWN_PAGE" }); ``` Building UI Components [#building-ui-components] Use exported components to build custom pages: ```tsx import { Header, Button, Text, useSupportText, useSupportNavigation } from "@cossistant/next"; const HelpPage = () => { const { goBack } = useSupportNavigation(); const text = useSupportText(); return ( <>

Help Center

); }; ``` Complete Example [#complete-example] ```tsx "use client"; import { Support, useSupportNavigation } from "@cossistant/next"; // 1. Extend types declare module "@cossistant/core" { interface RouteRegistry { FAQ: undefined; SETTINGS: { section: string }; } } // 2. Create pages const FAQPage = () => { const { goBack } = useSupportNavigation(); return (

FAQ

); }; const SettingsPage = ({ params }) => { const { navigate } = useSupportNavigation(); return (

Settings - {params?.section}

); }; // 3. Register and use export default function App() { return ( ); } // 4. Navigate from anywhere function MyButton() { const { navigate } = useSupportNavigation(); return ( ); } ``` Tips [#tips] **Keep it simple.** Start with ``, add custom pages only when needed. **Use type augmentation.** Always extend `RouteRegistry` for full type safety. **Leverage hooks.** Use `useSupportNavigation` for routing, `useSupportText` for i18n, `useSupport` for data. # Locale & Text URL: /docs/support-component/text Overview [#overview] The support widget ships with a fully typed translation layer powered by the `` component and the `useSupportText()` hook. Every key is defined in a single source of truth so you get auto-complete, variable hints and compiler errors if a required placeholder is missing. * Built-in locales: **English (`en`)**, **French (`fr`)** and **Spanish (`es`)** * Fallback order: provided `locale` prop → browser locale → English defaults * Keys always render with a `data-key-name="…"` attribute so you can discover them quickly in dev tools Rendering copy [#rendering-copy] Use the typed `` component for static markup or the hook when you need raw strings. ```tsx import { Text, useSupportText } from "@cossistant/next/support"; export const AskButton = () => { const text = useSupportText(); return ( ); }; ``` Both APIs are memo-friendly; the formatter is stable between renders and only re-computes when locale, overrides or context change. Providing locales & overrides [#providing-locales--overrides] Developers control the active locale and individual strings through the `Support` component. You can pass literal strings, per-locale overrides or formatter functions with full access to widget context (visitor, agents) plus utility helpers. ```tsx { const period = utils.timeOfDay(); const visitorName = variables?.visitorName ?? context.visitor?.contact?.name ?? ""; const prefix = period.token === "evening" ? "Bonsoir" : "Salut"; return `${prefix} ${visitorName}!`; }, "common.actions.askQuestion": { en: "Reach out", fr: "Contactez-nous", }, }} /> ``` Custom locales are supported too—just pass a new locale code and supply the strings under `content`. Debugging helpers [#debugging-helpers] Every rendered `` sets `data-key-name=""`, making it easy to inspect copy in the DOM. The translation utilities also expose helpers such as `formatNumber`, `pluralize`, `titleCase` and a `timeOfDay()` helper, available to formatter functions via the `utils` parameter. These guarantees keep the widget copy consistent, type-safe and ready for your team's voice. # Theme URL: /docs/support-component/theme Overview [#overview] The Support widget is **fully isolated** from your app's styles by default. All widget styles use the `co-` prefix, so they won't clash with your existing CSS. How Theming Works [#how-theming-works] The widget checks two places for every style value: 1. **Your override** (`--co-theme-*` variables) 2. **Built-in default** (sensible OKLCH colors) ```css /* Widget looks for --co-theme-primary first */ --co-primary: var(--co-theme-primary, oklch(20.5% 0 0)); ``` Quick Start [#quick-start] Override widget colors by setting `--co-theme-*` variables: ```css .cossistant { --co-theme-primary: #3b82f6; --co-theme-background: #ffffff; --co-theme-foreground: #0f172a; --co-theme-radius: 12px; } ``` Available Theme Variables [#available-theme-variables] | Variable | Default (Light) | Default (Dark) | | ------------------------------- | ------------------ | ------------------ | | `--co-theme-background` | `oklch(99% 0 0)` | `oklch(15.5% 0 0)` | | `--co-theme-foreground` | `oklch(14.5% 0 0)` | `oklch(98.5% 0 0)` | | `--co-theme-primary` | `oklch(20.5% 0 0)` | `oklch(98.5% 0 0)` | | `--co-theme-primary-foreground` | `oklch(98.5% 0 0)` | `oklch(20.5% 0 0)` | | `--co-theme-border` | `oklch(92.2% 0 0)` | `oklch(26.9% 0 0)` | | `--co-theme-muted` | Color-mixed | Color-mixed | | `--co-theme-muted-foreground` | Color-mixed | Color-mixed | | `--co-theme-radius` | `0.375rem` | `0.375rem` |

Background

oklch(99% 0 0)

Foreground

oklch(14.5% 0 0)

Primary

oklch(20.5% 0 0)

Border

oklch(92.2% 0 0)
Status Colors [#status-colors] | Variable | Default | | ------------------------ | --------------------------- | | `--co-theme-destructive` | `oklch(57.7% 0.245 27.325)` | | `--co-theme-success` | `oklch(71.7% 0.18 142)` | | `--co-theme-warning` | `oklch(86.4% 0.144 99)` | | `--co-theme-neutral` | `oklch(60.8% 0 0)` |

Destructive

oklch(57.7% 0.245 27.325)

Success

oklch(71.7% 0.18 142)

Warning

oklch(86.4% 0.144 99)

Neutral

oklch(60.8% 0 0)
Accent Colors [#accent-colors] These colors are used for avatar fallbacks when no profile image is available. | Variable | Default | | ------------------- | ------------------------ | | `--co-theme-pink` | `oklch(76.3% 0.152 354)` | | `--co-theme-yellow` | `oklch(86.4% 0.144 99)` | | `--co-theme-blue` | `oklch(72.5% 0.132 241)` | | `--co-theme-orange` | `oklch(74.5% 0.166 50)` |

Pink

oklch(76.3% 0.152 354)

Yellow

oklch(86.4% 0.144 99)

Blue

oklch(72.5% 0.132 241)

Orange

oklch(74.5% 0.166 50)
Light and Dark Mode [#light-and-dark-mode] Using the theme prop [#using-the-theme-prop] Force a specific theme regardless of your app's settings: ```tsx // Force dark theme // Force light theme (default) ``` Automatic theme detection (default) [#automatic-theme-detection-default] By default, the widget automatically adapts to your app's theme. It looks for: 1. `.dark` class on any parent element 2. `data-color-scheme="dark"` attribute on any parent element This works seamlessly with popular theme libraries like **next-themes** and **shadcn's theme system**. ```tsx // Widget adapts automatically // Or with data attribute
``` Using shadcn/ui Colors [#using-shadcnui-colors] Want the widget to match your shadcn/ui theme? Map shadcn variables to widget variables: ```css :root { --co-theme-background: var(--background); --co-theme-foreground: var(--foreground); --co-theme-primary: var(--primary); --co-theme-primary-foreground: var(--primary-foreground); --co-theme-border: var(--border); --co-theme-radius: var(--radius); } ``` This gives you full control—the widget only uses shadcn colors when you explicitly connect them. Custom Branding [#custom-branding] Match your brand identity with custom colors: ```css .cossistant { --co-theme-primary: #ff6b35; --co-theme-primary-foreground: #ffffff; --co-theme-background: #fafafa; --co-theme-foreground: #1a1a1a; --co-theme-border: #e5e5e5; --co-theme-radius: 16px; } ``` Advanced: Per-Shade Overrides [#advanced-per-shade-overrides] Fine-tune background shades for depth and hierarchy: ```css .cossistant { --co-theme-background-50: #fafafa; --co-theme-background-100: #f5f5f5; --co-theme-background-200: #eeeeee; --co-theme-background-300: #e0e0e0; } ``` By default, these are generated with `color-mix()` from your base colors. # Types URL: /docs/support-component/types Overview [#overview] This page is the canonical reference for shared Support component types. Other support docs link here when a table row references a named type like `TimelineItem`, `Conversation`, or `PublicVisitor`. Widget Types [#widget-types] DefaultMessage [#defaultmessage] Structure for pre-conversation welcome messages. VisitorMetadata [#visitormetadata] Key-value pairs for storing custom data about contacts. SenderType [#sendertype] Enum defining who can send messages. TriggerRenderProps [#triggerrenderprops] Props provided to custom trigger render functions. SupportHandle [#supporthandle] Imperative handle for programmatic widget control via refs. Visitor And Website Data [#visitor-and-website-data] PublicVisitor [#publicvisitor] The visitor object returned by the widget, representing an anonymous or identified visitor. PublicContact [#publiccontact] Contact information for an identified visitor. PublicWebsiteResponse [#publicwebsiteresponse] Website configuration and agent availability information. HumanAgent [#humanagent] Information about a human support agent. AIAgent [#aiagent] Information about an AI support agent. Conversations And Messages [#conversations-and-messages] Conversation [#conversation] Conversation record used throughout the support widget and event payloads. TimelineItem [#timelineitem] Timeline item payload used for widget messages, events, and AI tool output. Hook And Event Types [#hook-and-event-types] IdentifyParams [#identifyparams] Parameters for the `identify()` function. MessageComposer [#messagecomposer] State and actions returned by `useConversationPage` for message composition. SupportEvent [#supportevent] Union type of all possible widget events. Client Types [#client-types] CossistantClient [#cossistantclient] The low-level client instance for advanced programmatic control. The table below highlights the main public stores and methods exposed by the client. # What is Cossistant? URL: /docs/what **Own your support, either use our pre-built `` component, or build your own based on our headless components.** You know how most traditional support systems work: you load an external iframe and code within your app, making it harder to customize, use and even by time are being blocked by your users' ads blocker. This approach worked well until you need to customize your support and more recently, when you're trying to build AI agents that can truly be helpful with custom tools and logic. **You need control over the code to give true power to your agents.** This is what Cossistant aims to solve. It is built around the following principles: * **Open source:** You and the community can participate in making the tool safer and more powerful, no opaque black box. * **Open components:** Following shadcn/ui philosophy, every components used in `` are based on headless primitives available to you. * **Code first:** A single source of truth within your codebase to defined your agents and support behavior * **Beautiful Default:** The default `` comes with a carefully crafted support experience, powerful and beautiful as is * **AI-Ready:** Open code and code first for LLMs to read, understand, and improve.