npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@parlr/react-native

v0.1.7

Published

Official Parlr live chat SDK for React Native

Readme


Features

| Feature | Description | |---|---| | Real-time messaging | WebSocket with automatic reconnection and exponential backoff | | Optimistic UI | Messages appear instantly, sync in the background | | Typing indicators | Show when agents are typing, send visitor typing events | | Read receipts | Delivery status tracking (sendingsentread) | | Pre-built UI components | Drop-in <ParlrChat> and <ParlrConversationList> | | Pre-chat forms | Collect visitor info (name, email, phone) before the first message | | CSAT surveys | Satisfaction rating after conversation resolution | | Speech-to-text | Voice dictation via microphone with streaming transcription (iOS & Android) | | File attachments | Images and documents via expo-image-picker / expo-document-picker | | Rich messages | Cards, carousels, quick replies rendered natively | | Offline queue | Messages are queued and retried when connectivity returns | | Push notifications | FCM (Android) and APNs (iOS) support | | Device metadata | Platform, OS, screen size, locale, timezone — sent automatically | | User identification | Flexible: name, firstName/lastName, email, externalId | | HMAC verification | Prevent contact impersonation with server-side tokens | | Dark mode | Automatic system detection + full theme customization | | TypeScript-first | Complete type definitions for all APIs, hooks, and components | | Tiny footprint | ~160 KB published size, 1 runtime dependency (axios) |

Works with Expo (managed & bare) and bare React Native projects.


Installation

npm install @parlr/react-native
# or
yarn add @parlr/react-native

Required peer dependency

npm install react-native-reanimated

Optional dependencies

# Secure session persistence (recommended)
npm install expo-secure-store

# SVG icons (recommended — emoji fallback without it)
npm install react-native-svg

# File & image attachments
npm install expo-image-picker expo-document-picker

# Speech-to-text (voice dictation)
npm install expo-speech-recognition

Note: Without expo-secure-store, sessions are stored in memory and won't persist across app restarts. Users will get a new session each time.

Compatibility matrix

| Package | Minimum version | Required | |---|---|---| | react | 18.0.0 | Yes | | react-native | 0.72.0 | Yes | | react-native-reanimated | 3.0.0 | Yes | | expo-secure-store | 13.0.0 | No | | expo-image-picker | 15.0.0 | No | | react-native-svg | 13.0.0 | No | | expo-document-picker | 12.0.0 | No | | expo-speech-recognition | 1.0.0 | No |


Quick Start

1. Wrap your app with the provider

import { ParlrProvider } from '@parlr/react-native';

export default function App() {
  return (
    <ParlrProvider workspaceId="your-workspace-id">
      <Navigation />
    </ParlrProvider>
  );
}

2. Add the chat screen

import { ParlrChat } from '@parlr/react-native';

function SupportScreen({ navigation }) {
  return (
    <ParlrChat
      user={{
        email: '[email protected]',
        firstName: 'Alice',
        lastName: 'Martin',
      }}
      onBack={() => navigation.goBack()}
    />
  );
}

3. Show unread count anywhere

import { useParlr } from '@parlr/react-native';

function SupportButton() {
  const { unreadCount } = useParlr();

  return (
    <TouchableOpacity onPress={() => navigation.navigate('Support')}>
      <Text>Support</Text>
      {unreadCount > 0 && <Badge count={unreadCount} />}
    </TouchableOpacity>
  );
}

That's it. The SDK handles session management, WebSocket connections, message delivery, and UI rendering automatically.


Components

<ParlrProvider>

Root wrapper that initializes the SDK, manages the session, and opens the WebSocket connection. Place it near the top of your component tree (above any screen that uses Parlr hooks or components).

<ParlrProvider
  workspaceId="your-workspace-id"
  locale="en"
  debug={__DEV__}
  theme={{ colors: { primary: '#E91E63' } }}
  onError={(err) => Sentry.captureException(err)}
>
  <App />
</ParlrProvider>

| Prop | Type | Default | Description | |---|---|---|---| | workspaceId | string | required | Your Parlr workspace ID (find it in Settings > Channels > Mobile SDK) | | apiBaseUrl | string | https://api.parlr.chat/api/v1/widget | REST API base URL | | wsUrl | string | wss://ws.parlr.chat/ws | WebSocket endpoint | | locale | string | "fr" | BCP-47 locale code ("fr", "en", "es", "de", "ar") | | debug | boolean | false | Enable verbose console logging (API calls, WS events, session lifecycle) | | theme | Partial<ParlrTheme> | auto | Theme overrides merged with system light/dark theme (see Theming) | | identityToken | string | — | HMAC token for secure identity verification (see HMAC Verification) | | onError | (error: ParlrError) => void | — | Global error callback for monitoring |


<ParlrChat>

Full-featured chat screen. Handles session initialization, WebSocket, message sending/receiving, typing indicators, attachments, and CSAT surveys — all in one component.

<ParlrChat
  user={{
    email: '[email protected]',
    firstName: 'Alice',
    lastName: 'Martin',
    company: 'Acme Inc',
  }}
  onBack={() => navigation.goBack()}
  headerTitle="Help & Support"
  showPreChatForm={true}
  preChatFields={['name', 'email']}
  showSatisfactionSurvey={true}
/>

| Prop | Type | Default | Description | |---|---|---|---| | user | ParlrUser | — | Identify the user on mount (see User Identification) | | conversationId | string | — | Resume an existing conversation (omit to start fresh) | | onBack | () => void | — | Back button handler (shows back arrow when provided) | | headerTitle | string | "Support" | Header bar title | | placeholder | string | "Write a message..." | Input placeholder text | | accentColor | string | theme.colors.primary | Accent color override | | showHeader | boolean | true | Show/hide the header bar | | showPreChatForm | boolean | false | Collect user info before the first message | | preChatFields | Array<'name' \| 'email' \| 'phone'> | ['name', 'email'] | Which fields to show in the pre-chat form | | showSatisfactionSurvey | boolean | true | Show CSAT survey after conversation closes | | safeAreaBottom | number | 0 | Bottom safe-area inset so the input bar sits above the keyboard | | allowSpeechToText | boolean | true | Enable microphone button for voice dictation | | emptyStateTitle | string | auto | Custom title for the empty state (no messages yet) | | emptyStateDescription | string | auto | Custom description for the empty state | | onConversationClosed | () => void | — | Callback fired when a conversation is closed |

Built-in features: online/offline status indicator, three-dot menu (close/reopen), optimistic message sending, agent typing animation, attachment picker with preview, speech-to-text dictation, message pagination (infinite scroll), auto-scroll to latest, closed conversation banner, CSAT star rating.


<ParlrConversationList>

Displays a list of conversations with last message preview, unread badges, assignee avatar, and status indicator.

<ParlrConversationList
  onSelectConversation={(id) => navigation.navigate('Chat', { id })}
  onNewConversation={() => navigation.navigate('Chat')}
  statusFilter="open"
/>

| Prop | Type | Default | Description | |---|---|---|---| | onSelectConversation | (id: string) => void | required | Callback when tapping a conversation | | onNewConversation | () => void | — | "New conversation" button handler | | headerTitle | string | "Conversations" | Header title | | statusFilter | 'open' \| 'closed' \| 'pending' | — | Filter conversations by status | | showHeader | boolean | true | Show/hide header | | accentColor | string | "#6366f1" | Accent color |


Lower-level components

These are used internally by ParlrChat but are exported for building custom UIs:

| Component | Description | |---|---| | <ChatBubble message={msg} /> | Single message bubble (agent left, contact right) | | <TypingIndicator /> | Animated three-dot typing indicator | | <EmptyState /> | Welcome screen shown when no messages exist | | <PreChatForm onSubmit={fn} /> | Pre-chat information collection form | | <SatisfactionSurvey onSubmit={fn} /> | CSAT 1–5 star rating survey | | <AttachmentPicker onFilePicked={fn} /> | Photo / camera / document file picker | | <AttachmentPreview file={f} onSend={fn} /> | Attachment preview before sending | | <RichMessage content={rich} /> | Cards, carousels, quick replies | | <SpeechButton onTranscript={fn} /> | Microphone button with pulse animation for voice dictation |


Hooks

useParlr()

Access SDK state from any component inside <ParlrProvider>.

const {
  isReady,              // true when SDK is initialized and session is active
  isConnected,          // true when WebSocket is connected
  session,              // Current session (token, contactId, workspaceId)
  conversations,        // All conversations for this contact
  unreadCount,          // Total unread messages across all conversations
  identify,             // Identify or update the current user
  refreshConversations, // Force-refresh the conversation list
  theme,                // Resolved theme (light/dark + user overrides)
} = useParlr();

Identifying a user:

await identify({
  email: '[email protected]',
  firstName: 'Alice',
  lastName: 'Martin',
  company: 'Acme Inc',
  customAttributes: { plan: 'pro', mrr: 299 },
});

useChat(conversationId?)

Manage a single conversation with real-time updates.

const {
  messages,            // Message array (oldest first)
  isLoading,           // true during initial load
  hasError,            // true if an error occurred
  conversation,        // Active conversation metadata
  agentTyping,         // true when an agent is typing (auto-clears after 5s)
  sendMessage,         // Send a text message (appears instantly via optimistic UI)
  retryMessage,        // Retry a failed message by clientId
  notifyTyping,        // Send typing indicator to the server (debounced 3s)
  loadMore,            // Load older messages (pagination)
  hasMore,             // true if more pages are available
  closeConversation,   // Close the conversation
  reopenConversation,  // Reopen a closed conversation
} = useChat(conversationId);

Sending messages:

// Appears instantly with status "sending", then updates to "sent" on server confirmation
await sendMessage('Hello, I need help with my order');

// Retry a failed message
const failed = messages.find(m => m.status === 'failed');
if (failed?.clientId) {
  await retryMessage(failed.clientId);
}

How optimistic sending works:

  1. sendMessage() inserts the message locally with status sending
  2. The REST API is called in the background
  3. On success → status updates to sent
  4. On failure → status updates to failed (retryable up to 3 times with exponential backoff: 1s, 2s, 4s)
  5. When the same message arrives via WebSocket, it's deduplicated by clientId
  6. If no conversationId is provided, a new conversation is created automatically on the first sendMessage()

User Identification

The SDK supports flexible user identification through the ParlrUser interface. You can identify users in two ways:

Option 1: Using name (simple)

Pass a single name field. The SDK automatically splits it into firstName and lastName before sending to the server.

<ParlrChat
  user={{
    email: '[email protected]',
    name: 'Alice Martin',        // Automatically split: firstName="Alice", lastName="Martin"
  }}
/>

Splitting rules:

  • "Alice Martin"firstName: "Alice", lastName: "Martin"
  • "Alice"firstName: "Alice" (no lastName)
  • "Alice Marie Martin"firstName: "Alice", lastName: "Marie Martin"
  • " Alice Martin "firstName: "Alice", lastName: "Martin" (trimmed)

Option 2: Using firstName / lastName (explicit)

For precise control, pass firstName and lastName directly. These take priority over name.

<ParlrChat
  user={{
    email: '[email protected]',
    firstName: 'Alice',
    lastName: 'Martin',
  }}
/>

Important: If neither name, firstName, nor lastName are provided, the contact name in your Parlr dashboard will fall back to the email prefix (e.g., [email protected]alice.martin).

Full ParlrUser interface

interface ParlrUser {
  email?: string;                          // Contact email
  name?: string;                           // Full name (auto-split into firstName/lastName)
  firstName?: string;                      // First name (takes priority over name)
  lastName?: string;                       // Last name (takes priority over name)
  externalId?: string;                     // Your internal user ID
  phone?: string;                          // Phone number (E.164 format recommended)
  company?: string;                        // Company name
  customAttributes?: Record<string, unknown>; // Custom key-value pairs visible in the dashboard
}

Custom attributes

Custom attributes appear in the Contact Details panel of your Parlr dashboard:

await identify({
  email: '[email protected]',
  firstName: 'Alice',
  lastName: 'Martin',
  company: 'Acme Inc',
  customAttributes: {
    plan: 'enterprise',
    mrr: 2990,
    signupDate: '2025-06-15',
    accountManager: 'Bob',
  },
});

Device Metadata

The SDK automatically collects and sends device metadata when a session is created. No configuration needed.

| Data | Example | Dashboard panel | |---|---|---| | Platform | iOS, Android | Visitor Device | | OS version | 18.2, 35 (Android API) | Visitor Device | | Screen resolution | 430x932 | Visitor Device | | Locale | fr-FR | Location | | Timezone | Europe/Paris | Location | | SDK version | ParlrSDK/1.0 | User-Agent |

This data appears in the Visitor Device and Location sections of each contact's profile in the Parlr dashboard.


Theming

The SDK automatically detects your system's dark/light mode preference. You can override any theme value.

Brand color only (most common)

<ParlrProvider
  workspaceId="your-workspace-id"
  theme={{
    colors: {
      primary: '#E91E63',
      contactBubble: '#E91E63',
    },
  }}
>

Full custom theme

<ParlrProvider
  workspaceId="your-workspace-id"
  theme={{
    colors: {
      primary: '#FF6B00',
      primaryText: '#FFFFFF',
      background: '#1A1A2E',
      surface: '#16213E',
      text: '#EAEAEA',
      textSecondary: '#A0A0B0',
      border: '#2A2A4A',
      agentBubble: '#16213E',
      agentText: '#EAEAEA',
      contactBubble: '#FF6B00',
      contactText: '#FFFFFF',
    },
    borderRadius: { bubble: 20, input: 16, button: 12, avatar: 24 },
    spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
    typography: { headerSize: 20, bodySize: 16, captionSize: 12 },
  }}
>

Programmatic theme access

import { useParlr, defaultLightTheme, defaultDarkTheme, mergeTheme } from '@parlr/react-native';

// Access resolved theme in any component
const { theme } = useParlr();
<View style={{ backgroundColor: theme.colors.background }} />

// Build a theme from scratch
const custom = mergeTheme(defaultDarkTheme, { colors: { primary: '#FF6B00' } });
interface ParlrTheme {
  colors: {
    primary: string;         // Accent / brand color
    primaryText: string;     // Text on primary color
    background: string;      // Screen background
    surface: string;         // Card / input background
    surfaceDark: string;     // Darker surface (avatars, headers)
    text: string;            // Primary text color
    textSecondary: string;   // Secondary text, timestamps
    border: string;          // Borders, dividers
    success: string;         // Online indicator, success states
    error: string;           // Error states, failed messages
    agentBubble: string;     // Agent message bubble background
    agentBubbleDark: string; // Reserved
    agentText: string;       // Agent message text color
    agentTextDark: string;   // Reserved
    contactBubble: string;   // Contact message bubble background
    contactText: string;     // Contact message text color
  };
  borderRadius: {
    bubble: number;   // Message bubbles (default: 16)
    input: number;    // Text input (default: 12)
    button: number;   // Buttons (default: 8)
    avatar: number;   // Avatars (default: 20)
  };
  spacing: {
    xs: number;  // 4
    sm: number;  // 8
    md: number;  // 16
    lg: number;  // 24
    xl: number;  // 32
  };
  typography: {
    headerSize: number;   // 18
    bodySize: number;     // 15
    captionSize: number;  // 12
  };
}

Error Handling

The SDK exports a typed error hierarchy for precise error handling:

import {
  ParlrError,            // Base class for all SDK errors
  ParlrNetworkError,     // HTTP errors (includes statusCode property)
  ParlrAuthError,        // 401/403 — expired or invalid session
  ParlrValidationError,  // Malformed server response (includes field property)
  ParlrConnectionError,  // WebSocket errors (includes code, reason properties)
} from '@parlr/react-native';

Example: global error monitoring

<ParlrProvider
  workspaceId="your-workspace-id"
  onError={(error) => {
    if (error instanceof ParlrAuthError) {
      // Session expired — the SDK auto-refreshes, no action needed
    } else if (error instanceof ParlrNetworkError) {
      console.warn(`HTTP error: ${error.statusCode}`);
    } else if (error instanceof ParlrConnectionError) {
      console.warn(`WebSocket closed: ${error.code} ${error.reason}`);
    }
    // Send to your error tracking service
    Sentry.captureException(error);
  }}
>

Offline Support

Messages sent while offline are queued locally and automatically retried when connectivity returns.

import {
  queueMessage,
  getQueuedMessages,
  removeFromQueue,
  flushQueue,
} from '@parlr/react-native';

// Manually queue a message
queueMessage({
  conversationId: 'conv_123',
  content: 'Hello',
  clientId: 'uuid-v4',
  timestamp: Date.now(),
});

// Inspect the queue
const queued = await getQueuedMessages();
console.log(`${queued.length} messages pending`);

// Flush (send all queued messages)
await flushQueue(api);

Uses AsyncStorage when available, falls back to in-memory storage.


Push Notifications

Register device tokens for FCM (Android) or APNs (iOS) push notifications:

import { registerPushToken, unregisterPushToken } from '@parlr/react-native';

// After obtaining the device token from expo-notifications or @react-native-firebase/messaging
await registerPushToken(api, deviceToken, 'ios');    // or 'android'

// On logout or when the user disables notifications
await unregisterPushToken(api, deviceToken);

Speech-to-Text

The SDK supports voice dictation via the device microphone, powered by expo-speech-recognition. When installed, a microphone button appears in the input bar between the text field and the send button.

Installation

npm install expo-speech-recognition

iOS — add to your Info.plist:

<key>NSSpeechRecognitionUsageDescription</key>
<string>Used for voice dictation in the chat</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for voice dictation in the chat</string>

Android — add to your AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Usage (integrated)

The microphone button is shown by default in <ParlrChat>. Disable it with:

<ParlrChat allowSpeechToText={false} />

Usage (standalone)

import { SpeechButton, isSpeechAvailable } from '@parlr/react-native';

function MyCustomInput() {
  const [text, setText] = useState('');

  return (
    <View style={{ flexDirection: 'row' }}>
      <TextInput value={text} onChangeText={setText} />
      <SpeechButton
        onTranscript={setText}
        locale="en-US"
        accentColor="#6366f1"
        theme={theme}
      />
    </View>
  );
}

Graceful degradation

When expo-speech-recognition is not installed, <SpeechButton> returns null and the microphone button is automatically hidden. No error, no crash — the input bar works exactly as before.

import { isSpeechAvailable } from '@parlr/react-native';

if (isSpeechAvailable) {
  console.log('Speech-to-text is available');
}

Identity Verification (HMAC)

For production apps, use HMAC verification to prevent contact impersonation.

1. Generate the token server-side using your workspace secret:

// Node.js example
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', WORKSPACE_SECRET);
hmac.update(user.email);
const identityToken = hmac.digest('hex');

2. Pass it to the provider:

<ParlrProvider
  workspaceId="your-workspace-id"
  identityToken={identityTokenFromYourBackend}
>

The token is sent as X-Identity-Token on every request. The Parlr server validates it against your workspace secret before accepting the identification.


API Reference

Types

interface ParlrUser {
  email?: string;                             // Contact email address
  name?: string;                              // Full name (auto-split into firstName/lastName)
  firstName?: string;                         // First name (takes priority over name)
  lastName?: string;                          // Last name (takes priority over name)
  externalId?: string;                        // Your internal user ID
  phone?: string;                             // Phone number
  company?: string;                           // Company name
  customAttributes?: Record<string, unknown>; // Custom key-value pairs
}

Name resolution order:

  1. firstName / lastName if provided → used directly
  2. name if provided → auto-split on whitespace ("Alice Martin"firstName: "Alice", lastName: "Martin")
  3. Neither → dashboard shows email prefix as fallback
interface Session {
  token: string;        // JWT session token
  contactId: string;    // Unique contact ID
  workspaceId: string;  // Workspace ID
  expiresAt: string;    // ISO 8601 expiration timestamp
}
interface Conversation {
  id: string;
  status: 'open' | 'resolved' | 'pending' | 'closed';
  subject: string | null;
  lastMessageAt: string | null;              // ISO 8601
  unreadCount: number;
  assignee?: {
    id: string;
    name: string;
    avatarUrl?: string;
  } | null;
}
interface Message {
  id: string;
  conversationId: string;
  senderType: 'contact' | 'agent';
  senderName?: string;
  senderAvatarUrl?: string;
  content: string | null;                                   // Null for image/attachment-only messages
  createdAt: string;                                      // ISO 8601
  clientId?: string;                                      // For optimistic deduplication
  status?: 'sending' | 'sent' | 'failed' | 'read';
  attachments?: Attachment[];
  contentType?: 'text' | 'image' | 'file' | 'rich';
  metadata?: Record<string, unknown>;
}
interface Attachment {
  id: string;
  name: string;          // Original filename
  url: string;           // Download URL
  contentType: string;   // MIME type (e.g., "image/png")
  size: number;          // Size in bytes
}
interface RichContent {
  type: 'card' | 'carousel' | 'quick_replies' | 'buttons';
  card?: RichCard;
  carousel?: { cards: RichCard[] };
  quickReplies?: { text?: string; replies: Array<{ label: string; value: string }> };
  buttons?: RichAction[];
}

interface RichCard {
  title?: string;
  description?: string;
  imageUrl?: string;
  actions?: RichAction[];
}

interface RichAction {
  type: 'link' | 'postback' | 'reply';
  label: string;
  value: string;
}
interface WsEventMap {
  auth_ok: { contactId: string };
  auth_error: { code?: number; reason?: string; message?: string };
  new_message: WsNewMessagePayload;
  typing_start: { conversationId: string; senderType?: 'agent' | 'contact'; senderName?: string };
  typing_stop: { conversationId: string; senderType?: 'agent' | 'contact'; senderName?: string };
  message_seen: { conversationId: string; messageId: string; seenBy: string };
  conversation_updated: { conversationId: string; status?: string; assignee?: object | null };
  pong: {};
  disconnected: { code?: number; reason?: string };
  error: { code?: string; message?: string };
}

Connection behavior:

  • Auto-reconnect with exponential backoff (1s → 30s max)
  • Keep-alive ping every 25s, server timeout at 50s
  • Clean listener teardown on component unmount

All exports

// Components
import {
  ParlrProvider,
  ParlrChat,
  ParlrConversationList,
  ChatBubble,
  TypingIndicator,
  EmptyState,
  PreChatForm,
  SatisfactionSurvey,
  AttachmentPicker,
  AttachmentPreview,
  RichMessage,
  SpeechButton,
} from '@parlr/react-native';

// Hooks
import { useParlr, useChat } from '@parlr/react-native';

// Errors
import {
  ParlrError,
  ParlrNetworkError,
  ParlrAuthError,
  ParlrValidationError,
  ParlrConnectionError,
} from '@parlr/react-native';

// Theme utilities
import { defaultLightTheme, defaultDarkTheme, mergeTheme } from '@parlr/react-native';

// Offline queue
import { queueMessage, getQueuedMessages, removeFromQueue, flushQueue } from '@parlr/react-native';

// Push notifications
import { registerPushToken, unregisterPushToken } from '@parlr/react-native';

// Speech-to-text
import { SpeechButton, isSpeechAvailable } from '@parlr/react-native';

Examples

Minimal integration

import { ParlrProvider, ParlrChat } from '@parlr/react-native';

export default function App() {
  return (
    <ParlrProvider workspaceId="your-workspace-id">
      <ParlrChat />
    </ParlrProvider>
  );
}

With React Navigation

// App.tsx
import { ParlrProvider } from '@parlr/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <ParlrProvider workspaceId="your-workspace-id" locale="en">
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen
            name="Support"
            component={SupportScreen}
            options={{ headerShown: false }}
          />
        </Stack.Navigator>
      </NavigationContainer>
    </ParlrProvider>
  );
}

// HomeScreen.tsx
import { useParlr } from '@parlr/react-native';

function HomeScreen({ navigation }) {
  const { unreadCount } = useParlr();

  return (
    <View>
      <TouchableOpacity onPress={() => navigation.navigate('Support')}>
        <Text>Contact Support {unreadCount > 0 ? `(${unreadCount})` : ''}</Text>
      </TouchableOpacity>
    </View>
  );
}

// SupportScreen.tsx
import { ParlrChat } from '@parlr/react-native';

function SupportScreen({ navigation }) {
  return (
    <ParlrChat
      user={{
        email: '[email protected]',
        firstName: 'Alice',
        lastName: 'Martin',
      }}
      onBack={() => navigation.goBack()}
      headerTitle="Help & Support"
      showPreChatForm={true}
    />
  );
}

With Expo Router

// app/_layout.tsx
import { ParlrProvider } from '@parlr/react-native';
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <ParlrProvider workspaceId="your-workspace-id" locale="fr">
      <Stack />
    </ParlrProvider>
  );
}

// app/support.tsx
import { ParlrChat } from '@parlr/react-native';
import { useRouter } from 'expo-router';

export default function SupportScreen() {
  const router = useRouter();

  return (
    <ParlrChat
      user={{
        email: '[email protected]',
        name: 'Alice Martin',
      }}
      onBack={() => router.back()}
    />
  );
}

Conversation list + detail

import { ParlrConversationList, ParlrChat } from '@parlr/react-native';

// List screen
function ConversationsScreen({ navigation }) {
  return (
    <ParlrConversationList
      onSelectConversation={(id) => navigation.navigate('Chat', { conversationId: id })}
      onNewConversation={() => navigation.navigate('Chat')}
    />
  );
}

// Chat screen
function ChatScreen({ route, navigation }) {
  return (
    <ParlrChat
      conversationId={route.params?.conversationId}
      onBack={() => navigation.goBack()}
    />
  );
}

Custom chat UI with hooks

import { useChat, ChatBubble, TypingIndicator } from '@parlr/react-native';
import { FlatList, TextInput, TouchableOpacity, View, Text } from 'react-native';
import { useState } from 'react';

function CustomChat({ conversationId }) {
  const [text, setText] = useState('');
  const {
    messages, agentTyping, sendMessage, notifyTyping, loadMore, hasMore,
  } = useChat(conversationId);

  const handleSend = async () => {
    if (!text.trim()) return;
    await sendMessage(text.trim());
    setText('');
  };

  return (
    <View style={{ flex: 1 }}>
      <FlatList
        data={messages}
        inverted
        keyExtractor={(m) => m.id}
        renderItem={({ item }) => <ChatBubble message={item} />}
        onEndReached={() => hasMore && loadMore()}
      />
      {agentTyping && <TypingIndicator />}
      <View style={{ flexDirection: 'row', padding: 8 }}>
        <TextInput
          value={text}
          onChangeText={(t) => { setText(t); notifyTyping(true); }}
          style={{ flex: 1, borderWidth: 1, borderRadius: 8, padding: 8 }}
          placeholder="Type a message..."
        />
        <TouchableOpacity onPress={handleSend} style={{ padding: 8 }}>
          <Text>Send</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

Advanced Configuration

Self-hosted Parlr

Point the SDK to your own Parlr instance:

<ParlrProvider
  workspaceId="your-workspace-id"
  apiBaseUrl="https://api.your-domain.com/api/v1/widget"
  wsUrl="wss://ws.your-domain.com/ws"
>

Debug mode

<ParlrProvider workspaceId="your-workspace-id" debug={true}>

Logs to the console:

  • REST API calls and responses
  • WebSocket events (connect, disconnect, messages)
  • Session lifecycle (create, refresh, expire)
  • Retry attempts with backoff timing

FAQ

Sign up at app.parlr.chat, create a workspace, and find your workspace ID in Settings > Channels > Mobile SDK.

Yes. The SDK works with both Expo managed workflow and bare React Native. All optional dependencies (expo-secure-store, expo-image-picker, expo-document-picker) are standard Expo packages.

Messages are queued locally and automatically retried when connectivity returns. The WebSocket reconnects with exponential backoff (1s up to 30s). The UI continues to work normally — messages show with sending status until confirmed.

Yes. Use useParlr() and useChat() hooks to build a completely custom UI. The SDK handles all networking, state management, and real-time events for you.

Generate an HMAC-SHA256 token server-side using your workspace secret and the user's email. Pass it as identityToken to <ParlrProvider>. The server validates the token before accepting the identification — this prevents visitors from impersonating other contacts.

Make sure you're passing firstName/lastName (or name) in the user prop. If only email is provided, the dashboard falls back to displaying the email prefix (e.g., alice from [email protected]).


Changelog

0.1.7

  • Fix: Speech-to-text API — corrected expo-speech-recognition integration to use ExpoSpeechRecognitionModule.addListener() instead of the non-existent addSpeechRecognitionListener export. This fixes the addSpeechRecognitionListener is not a function crash when pressing the microphone button.

0.1.6

  • Speech-to-text — voice dictation via expo-speech-recognition (optional peer dependency). A microphone button with pulse animation appears in the input bar. Streaming interim results are displayed in real-time.
  • New <SpeechButton> component for standalone usage.
  • New isSpeechAvailable utility to check availability at runtime.
  • New allowSpeechToText prop on <ParlrChat> (default: true).
  • New MicrophoneIcon SVG icon with emoji fallback.

0.1.5

  • Fix: ChatBubble crash on image messages — fixed Cannot read property 'length' of null when a message contains only an image attachment (no text). The Message.content type is corrected from string to string | null to reflect the actual API contract. ChatBubble now handles null content gracefully.

0.1.4

  • SVG icons — all UI icons now use inline SVGs via react-native-svg (optional) instead of emoji characters. Falls back to emoji gracefully if react-native-svg is not installed.

0.1.3

  • Name auto-split — when passing name (e.g., "Alice Martin") without firstName/lastName, the SDK now automatically splits it into firstName and lastName before sending to the server. This fixes an issue where the Parlr dashboard would show only the email prefix instead of the full name.

0.1.2

  • Documentation improvements and updated metadata.

0.1.1

  • Device metadata — the SDK now automatically collects and sends device information (platform, OS version, screen resolution, locale, timezone) when creating a session. This data appears in the Visitor Device and Location panels of your Parlr dashboard.
  • Internal improvements to session creation payload.

0.1.0

  • Initial release with full chat UI, WebSocket real-time messaging, theming system, hooks API, offline queue, push notifications, pre-chat forms, CSAT surveys, file attachments, rich messages, and TypeScript support.

Support


License

MIT © Parlr