# Cossistant documentation
URL: /docs
## Quickstart with your framework
Start by selecting your framework of choice, then follow the instructions to install the dependencies and structure your app. Cossistant works with all React frameworks.
Next.js
Add Cossistant
{``}
widget to your Next.js project.
React [soon]
Add Cossistant {``} widget to any react compatible framework.
# 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.
# Contacts
URL: /docs/concepts/contacts
## What are Contacts?
**Contacts** are identified [visitors](/docs/concepts/visitors). 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
### 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
```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
```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
Metadata provides context to support agents during conversations. It appears in your dashboard alongside chat threads.
### 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
```typescript
type VisitorMetadata = Record;
```
Only primitive values are supported—no nested objects or arrays.
### 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
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
A single contact can have multiple [visitors](/docs/concepts/visitors) 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
* **[Visitors](/docs/concepts/visitors)**: 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?
**Conversations** are threaded chat sessions between [visitors](/docs/concepts/visitors) (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
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
### 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
```
open → resolved
↓ ↑
└───────┘ (can be reopened)
```
* **open**: Active conversation requiring attention
* **resolved**: Conversation marked as complete (can be reopened)
### 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
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
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
```
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
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
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
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
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/visitors) have device-specific conversations.
## Learn More
* **[Timeline Items](/docs/concepts/timeline-items)**: Building blocks of conversations
* **[Visitors](/docs/concepts/visitors)**: Anonymous users who start conversations
* **[Contacts](/docs/concepts/contacts)**: Identified users with cross-device history
# Visitors
URL: /docs/concepts
## 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
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
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
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
Transform anonymous visitors into identified [contacts](/docs/concepts/contacts) when users authenticate:
### 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 (
)}
);
```
### Bubble Render Props
The `Primitive.Bubble` component provides:
| 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 |
## Custom Container
Replace the widget container to control layout and animations:
```tsx
import { Support, type ContainerSlotProps } from "@cossistant/react";
const CustomContainer = ({
className,
children,
position,
align
}: ContainerSlotProps) => (
{children}
);
```
## Custom Router
For complete control over routing logic:
```tsx
import { Support, type RouterSlotProps, useSupportNavigation } from "@cossistant/react";
import * as Primitive from "@cossistant/react/primitives";
const CustomRouter = ({ children }: RouterSlotProps) => {
const { current } = useSupportNavigation();
return (
<>
{children}
>
);
};
```
## Slot Types Reference
All slot components receive typed props:
```tsx
type BubbleSlotProps = {
className?: string;
};
type ContainerSlotProps = {
className?: string;
children: React.ReactNode;
position?: "top" | "bottom";
align?: "right" | "left";
};
type RouterSlotProps = {
children?: React.ReactNode;
};
```
## Full Example
Combine multiple customizations:
```tsx
import { Support } from "@cossistant/react";
import * as Primitive from "@cossistant/react/primitives";
const MyBubble = ({ className }) => (
{({ isOpen }) => {isOpen ? "×" : "?"}}
);
export default function App() {
return (
);
}
```
# Hooks
URL: /docs/support-component/hooks
## Overview
Cossistant provides React hooks for programmatic control over the support widget and visitor identification. These hooks are ideal when you need to integrate support functionality into custom components or respond to application events.
## useSupport
Access support widget state and controls from any client component.
### 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
void",
required: true,
returns: "void",
},
close: {
description: "Function to close the support widget",
type: "() => void",
required: true,
returns: "void",
},
toggle: {
description: "Function to toggle the widget open/closed state",
type: "() => void",
required: true,
returns: "void",
},
unreadCount: {
description: "Number of unread messages across all conversations",
type: "number",
required: true,
},
availableHumanAgents: {
description: "List of human support agents currently available",
type: "HumanAgent[]",
typeDescriptionLink: "#humanagent",
required: true,
},
availableAIAgents: {
description: "List of AI agents configured for support",
type: "AIAgent[]",
typeDescriptionLink: "#aiagent",
required: true,
},
client: {
description: "CossistantClient instance for direct API access",
type: "CossistantClient",
typeDescriptionLink: "#cossistantclient",
required: true,
},
isLoading: {
description: "Whether website data is still loading",
type: "boolean",
required: true,
},
error: {
description: "Error object if website data failed to load",
type: "Error | null",
required: true,
},
size: {
description: "Widget size configuration",
type: '"normal" | "larger"',
required: true,
},
}}
/>
## 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
```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
```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
Promise<...>",
typeDescription:
"(params: IdentifyParams) => Promise<{ contactId: string; visitorId: string } | null>",
required: true,
parameters: [
{
name: "params",
description:
"Identification parameters including externalId, email, name, image, and metadata. See IdentifyParams type below.",
},
],
returns:
"Promise<{ contactId: string; visitorId: string } | null> - Returns the contact and visitor IDs on success, or null on failure",
},
setVisitorMetadata: {
description: "Update metadata for the identified contact",
type: "(metadata: VisitorMetadata) => Promise<...>",
typeDescription:
"(metadata: VisitorMetadata) => Promise",
required: true,
parameters: [
{
name: "metadata",
description:
"Metadata object to merge into the contact's existing metadata. See VisitorMetadata type below.",
},
],
returns:
"Promise - Returns the updated visitor object on success, or null on failure",
},
}}
/>
### identify() Parameters
",
typeDescriptionLink: "#visitormetadata",
required: false,
},
}}
/>
Prefer declarative code?
Use the{" "}
IdentifySupportVisitor
{" "}
component for a simpler, declarative approach to visitor identification in
Server Components.
## Types
### PublicVisitor
The visitor object returned by the widget, representing an anonymous or identified visitor.
### PublicContact
Contact information for an identified visitor.
### PublicWebsiteResponse
Website configuration and agent availability information.
### HumanAgent
Information about a human support agent.
### AIAgent
Information about an AI support agent.
### CossistantClient
The underlying client instance for direct API access. Used for advanced programmatic control.
### IdentifyParams
Parameters for the identify() function.
",
typeDescriptionLink: "#visitormetadata",
required: false,
},
}}
/>
### VisitorMetadata
Key-value pairs for storing custom data about contacts.
**Type Definition:** `Record`
### SenderType
Enum defining who can send messages.
**Type Definition:** `"visitor" | "team_member" | "ai"`
# Basic usage
URL: /docs/support-component
## Philosophy
The `` component is built on the principle of **simplicity first**. Drop it into your React app and get a fully functional support system without configuration overhead.
## How it Works
* **Zero Config**: Works out of the box with sensible defaults
* **Progressive Enhancement**: Start simple, customize as needed
* **Real-time Chat**: Built-in WebSocket support for live conversations
* **Smart Routing**: Automatically routes conversations to appropriate channels
* **Headless Architecture**: Full control through primitive components when needed
The component handles the complexity of modern support systems while maintaining a clean, minimal API surface.
## Customizing Messages & Quick Options
Use the `` component to customize default messages and quick options per page or route. This is perfect for providing context-specific help.
### Example: Pricing Page
```tsx showLineNumbers title="app/pricing/page.tsx"
import { SupportConfig } from "@cossistant/next";
export default function PricingPage() {
return (
<>
Choose your plan
{/* rest of pricing page */}
>
);
}
```
The `` component accepts the following props:
## Identifying Visitors
By default, visitors are **anonymous**. Use `` to connect them to your authenticated users, transforming them into [contacts](/docs/concepts/contacts).
Once identified, all conversations and metadata are linked to the contact across devices.
### Example: Dashboard Layout with Better Auth
```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,
}: {
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
",
typeDescriptionLink: "#visitormetadata",
required: false,
},
}}
/>
Need programmatic control?
Use the{" "}
useVisitor hook
to identify visitors or update metadata from client components.
## Support Component Props
The `` component accepts these optional props to customize its appearance and behavior:
, container?: ComponentType, router?: ComponentType }",
required: false,
},
classNames: {
description:
"Granular className overrides for specific parts of the widget",
type: "object",
typeDescription: "{ root?: string, bubble?: string, container?: string }",
required: false,
},
children: {
description: "Declarative Page components to register custom routes",
type: "React.ReactNode",
required: false,
},
quickOptions: {
description: "Quick reply options for this specific widget instance",
type: "string[]",
required: false,
},
defaultMessages: {
description: "Welcome messages shown before conversation starts",
type: "DefaultMessage[]",
typeDescriptionLink: "#defaultmessage",
required: false,
},
defaultOpen: {
description: "Whether the widget should open automatically on mount",
type: "boolean",
required: false,
},
locale: {
description:
"Locale string for widget translations (e.g., 'en', 'fr', 'es')",
type: "string",
required: false,
},
content: {
description: "Custom text content overrides for widget copy",
type: "Partial",
typeDescription:
"Partial record of all text strings used in the widget for internationalization and customization",
required: false,
},
}}
/>
### Usage Examples
Basic customization with theme and styling:
```tsx
```
With custom page:
```tsx
import { Support, Page, useSupportNavigation } from "@cossistant/next";
const HelpPage = () => {
const { goBack } = useSupportNavigation();
return (
Help Center
);
};
export default function App() {
return (
);
}
```
For advanced customization, see the [Custom Components](/docs/support-component/custom-components) guide.
## Types
### DefaultMessage
Structure for pre-conversation welcome messages.
### VisitorMetadata
Key-value pairs for storing custom data about contacts.
**Type Definition:** `Record`
### SenderType
Enum defining who can send messages.
**Type Definition:** `"visitor" | "team_member" | "ai"`
# Primitives
URL: /docs/support-component/primitives
## 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.
## Quick Example
```tsx
import * as Primitive from "@cossistant/react/primitives";
{({ isOpen, close }) => (
)}
{({ toggle }) => (
)}
```
## Primitives Reference
### Layout & Structure
**``**
Context provider for declarative page registration. Wrap your app to enable `` and ``.
```tsx
```
**``**
Generic router that renders registered pages.
```tsx
```
**``**
Declaratively register a page component.
```tsx
```
**``**
Dialog container with open/close state and escape key handling.
```tsx
{({ isOpen, close }) => (
{isOpen &&
Dialog content
}
)}
```
**``**
Floating action button with widget state.
```tsx
{({ isOpen, unreadCount, isTyping, toggle }) => (
)}
```
### Timeline & Messages
**``**
Message timeline with automatic scrolling and loading states.
```tsx
{(item) => }
```
**``**
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
**``**
Rich text input with file upload support.
```tsx
```
**``**
File upload component with drag-and-drop.
```tsx
```
**`