chatwoot/chatwoot

Open-source live-chat, email support, omni-channel desk. An alternative to Intercom, Zendesk, Salesforce Service Cloud etc. 🔥💬

28,643 stars Ruby 10 components

12 hidden assumptions · 6-stage pipeline · 10 components

Like any codebase, this fullstack makes assumptions it never checks — most are routine. The ones worth your attention are below.

Routes customer conversations across channels to agents with omnichannel inbox management

Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes.

Under the hood, the system uses 3 feedback loops, 4 data pools, 4 control points to manage its runtime behavior.

A 10-component fullstack. 2979 files analyzed. Data flows through 6 distinct pipeline stages.

Hidden Assumptions

Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here. The rest are minor; they're under "Show everything".

Worth your attention first

If server returns conversations with different schemas (missing properties, different structure), the merge operation will create conversations with undefined properties or overwrite important data, breaking real-time message display

Worth your attention first

If contact IDs are invalid, missing, or point to the same contact, ContactMergeAction will receive nil objects or attempt to merge a contact with itself, likely causing database constraint violations or data corruption

Worth your attention first

If database schema changes column names, API serialization changes field names, or internationalization affects attribute keys, contact filtering will silently fail to match any records

Show everything (9 more)
Ordering

Assumes conversationsForADate array is already sorted by timestamp in ascending order for avatar display logic - compares current message with next message at index+1 to determine if sender changed

If this fails: If messages arrive out of order or array is not pre-sorted, avatar display logic will be incorrect, showing/hiding avatars at wrong positions and creating confusing conversation flows

app/javascript/widget/store/modules/conversation/helpers.js:groupConversationBySender
Environment

Assumes IndexedDB is available and functional, only checks for initialization failure but not for subsequent database corruption, quota exceeded, or browser security policies that disable IndexedDB

If this fails: Database operations after initialization can fail silently, causing cache reads to return empty arrays while cache keys indicate valid data exists, leading to unnecessary API calls and stale UI state

app/javascript/dashboard/api/CacheEnabledApiClient.js:getFromCache
Temporal

Assumes cache keys from server API remain consistent between the initial cache_keys request and when cached data is actually used - no validation that cache hasn't been invalidated during the multi-step cache validation process

If this fails: Race condition where cache key validates as current but server data changes before cached data is returned, causing dashboard to display stale information until next cache refresh cycle

app/javascript/dashboard/api/CacheEnabledApiClient.js:validateCacheKey
Domain

City filter configuration has inconsistent dataType 'Number' (capitalized) while other location/text fields use 'text' or 'number' - assumes this dataType mismatch doesn't affect filter operator behavior or data validation

If this fails: City filters may not work correctly with text-based city names, causing type coercion errors or preventing users from filtering contacts by city location

app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js:city
Resource

Assumes unlimited memory for storing all conversations in browser memory via allConversations array, with no upper limit or cleanup mechanism for long-running dashboard sessions

If this fails: In busy customer support environments, the conversation store will grow unbounded, eventually causing browser memory exhaustion, tab crashes, and degraded performance as conversation count increases

app/javascript/dashboard/store/modules/conversations/index.js:allConversations
Contract

Assumes authentication mechanism is mutually exclusive - if access token headers are present, user session authentication is bypassed entirely, with no validation that access token is actually valid before skipping session checks

If this fails: Malformed or invalid access tokens in headers will bypass session authentication, potentially allowing unauthenticated requests to proceed until validate_bot_access_token! is called, creating security gaps

app/controllers/api/base_controller.rb:authenticate_by_access_token?
Shape

Assumes label parameter is a single string value that can be safely URL-encoded with labels[]= format, but doesn't validate if label contains characters that break URL parameter parsing

If this fails: Contact labels containing special characters (=, &, %, spaces) will malform the query string, causing contact filtering by labels to return incorrect results or server parsing errors

app/javascript/dashboard/api/contacts.js:buildContactParams
Temporal

Assumes system clock is accurate and Date.getTime() provides sufficient uniqueness for temporary message IDs when combined with getUuid(), but doesn't handle clock skew or rapid message creation scenarios

If this fails: If system clock jumps backward or multiple messages are created in rapid succession, timestamp-based logic for message ordering could fail, displaying messages out of sequence in widget conversations

app/javascript/widget/store/modules/conversation/helpers.js:createTemporaryMessage
Environment

Assumes store.dispatch('setUser') always resolves successfully and that getCurrentUser getter returns consistent user object structure with accounts array and account_id properties

If this fails: If user authentication fails or user object has unexpected structure, route navigation will redirect to wrong routes or cause JavaScript errors, potentially leaving users stuck on authentication pages

app/javascript/dashboard/routes/index.js:validateAuthenticateRoutePermission

Open the standalone hidden-assumptions report for chatwoot →

How Data Flows Through the System

Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes.

  1. Customer initiates conversation — Widget or channel webhook creates Contact and Conversation records, validates inbox settings and routing rules
  2. Message processing and validation — Incoming messages are validated, processed for content analysis, attached to conversation, and prepared for agent delivery [Message → Message]
  3. Real-time dashboard updates — ActionCable broadcasts message events to agent dashboard where ConversationStore mutations update Vue component state [Message → ConversationState] (config: default.channel_prefix)
  4. Agent response and routing — ConversationApi handles agent replies, status updates, and assignments through Rails controllers to appropriate delivery channels [ConversationState → Message]
  5. Contact management and search — ContactAPI processes contact filtering, merging, and search operations with pagination and label-based organization [Contact → ContactState]
  6. Cache and data persistence — CacheEnabledApiClient validates cache keys against server, uses IndexedDB for offline data access, and refreshes stale cache [API responses → Cached data]

Data Models

The data structures that flow between stages — the contracts that hold the system together.

Conversation app/models/conversation.rb
ActiveRecord model with id, account_id, inbox_id, status (open/resolved/pending), priority, assignee_id, contact_id, messages, labels, created_at, updated_at
Created when contact initiates conversation, assigned to agents, updated with messages and status changes, archived when resolved
Contact app/models/contact.rb
ActiveRecord model with id, account_id, name, email, phone_number, identifier, custom_attributes hash, created_at, last_activity_at
Created on first interaction, updated with each message or agent action, can be merged with duplicates
Message app/models/message.rb
ActiveRecord model with id, conversation_id, content, message_type (incoming/outgoing), sender_type, sender_id, attachments, created_at, content_attributes
Created when customer or agent sends message, processed for content analysis, delivered via WebSocket to dashboard
Inbox app/models/inbox.rb
ActiveRecord model with id, account_id, name, channel_type (web_widget, email, api, etc), settings hash, greeting_enabled, working_hours
Created to connect communication channel, configured with routing rules, used to route incoming conversations to appropriate agents
ConversationState app/javascript/dashboard/store/modules/conversations/index.js
Vuex state with allConversations array, selectedChatId, chatStatusFilter, appliedFilters array, currentInbox, attachments object
Loaded on dashboard init, updated via WebSocket events, filtered and sorted by user actions
ContactState app/javascript/dashboard/store/modules/contacts/index.js
Vuex state with records object (contacts by id), meta (count, currentPage, hasMore), uiFlags (loading states), appliedFilters array
Populated on contact list load, updated when contacts are created/modified, filtered by search and label criteria

System Behavior

How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.

Data Pools

ConversationStore (state-store)
Accumulates all active conversations with messages, status, and agent assignments for dashboard display
IndexedDB Cache (cache)
Stores API responses locally with cache keys for offline access and reduced server load
Rails Database (database)
Persists all conversation, contact, message, and user data with relationships and constraints
ActionCable Channel (queue)
Broadcasts real-time updates to connected agent dashboards via Redis-backed WebSocket channels

Feedback Loops

Delays

Control Points

Technology Stack

Ruby on Rails (framework)
Backend API framework handling conversations, contacts, and user management
Vue.js (framework)
Frontend framework for agent dashboard with reactive conversation and contact interfaces
Vuex (library)
State management for dashboard data with modules for conversations, contacts, and UI state
ActionCable (framework)
WebSocket implementation for real-time conversation updates to agent dashboards
Redis (database)
Backs ActionCable channels and provides caching layer for conversation data
PostgreSQL (database)
Primary database storing conversations, contacts, messages, and user accounts
IndexedDB (database)
Client-side storage for caching API responses to reduce server load and enable offline access
Pundit (library)
Authorization framework controlling access to conversations and account resources
Vite (build)
Frontend build tool for Vue.js dashboard and embeddable widget compilation
Sidekiq (library)
Background job processing for email delivery, webhooks, and async data operations

Key Components

Explore the interactive analysis

See the full architecture map, data flow, and code patterns visualization.

Analyze on CodeSea

Related Fullstack Repositories

Frequently Asked Questions

What is chatwoot used for?

Routes customer conversations across channels to agents with omnichannel inbox management chatwoot/chatwoot is a 10-component fullstack written in Ruby. Data flows through 6 distinct pipeline stages. The codebase contains 2979 files.

How is chatwoot architected?

chatwoot is organized into 6 architecture layers: API Controllers, Service Objects, Models, Background Jobs, and 2 more. Data flows through 6 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.

How does data flow through chatwoot?

Data moves through 6 stages: Customer initiates conversation → Message processing and validation → Real-time dashboard updates → Agent response and routing → Contact management and search → .... Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes. This pipeline design reflects a complex multi-stage processing system.

What technologies does chatwoot use?

The core stack includes Ruby on Rails (Backend API framework handling conversations, contacts, and user management), Vue.js (Frontend framework for agent dashboard with reactive conversation and contact interfaces), Vuex (State management for dashboard data with modules for conversations, contacts, and UI state), ActionCable (WebSocket implementation for real-time conversation updates to agent dashboards), Redis (Backs ActionCable channels and provides caching layer for conversation data), PostgreSQL (Primary database storing conversations, contacts, messages, and user accounts), and 4 more. This broad technology surface reflects a mature project with many integration points.

What system dynamics does chatwoot have?

chatwoot exhibits 4 data pools (ConversationStore, IndexedDB Cache), 3 feedback loops, 4 control points, 3 delays. The feedback loops handle auto-scale and cache-invalidation. These runtime behaviors shape how the system responds to load, failures, and configuration changes.

What design patterns does chatwoot use?

5 design patterns detected: Service Object Pattern, API Client Abstraction, Vuex Store Modules, Real-time Event Broadcasting, Multi-layer Caching.

Analyzed on April 20, 2026 by CodeSea. Written by .