Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jAtInn71/chatwoot-costom/llms.txt

Use this file to discover all available pages before exploring further.

The widget customization layer lives entirely inside custom/widget/. Each file there is a Vue component or i18n resource that overrides its upstream Chatwoot equivalent via a Dockerfile COPY directive, so upstream source files are never modified. Any change to widget components requires a Vite rebuild (./build.sh) followed by a Docker image rebuild before it takes effect.

Branding override

Upstream Chatwoot renders a “Powered by Chatwoot” footer at the bottom of the widget, wrapped in an anchor tag pointing to https://www.chatwoot.com. Even if the i18n key POWERED_BY is relabelled, the outbound link remains in the original component. This fork replaces the entire component with custom/widget/components/Branding.vue, which renders a plain <span> — no anchor, no Chatwoot logo, no outbound navigation. The Dockerfile maps this file onto the upstream component path at build time. The display text is controlled by the POWERED_BY key in custom/widget/i18n/en.json:
"POWERED_BY": "Powered by Visual Graphx"
To change the branding label, update that key and rebuild.

Chat input enhancements

custom/widget/components/ChatInputWrap.vue wraps the text input and its action buttons. The microphone button (ElevenLabsVoiceButton) is mounted here, next to the emoji picker button:
<!-- custom/widget/components/ChatInputWrap.vue:174-177 -->
<ElevenLabsVoiceButton
  class="text-n-slate-12"
  size="medium"
/>
The button is always present in the DOM — ElevenLabsVoiceButton itself gates visibility on three conditions being true simultaneously:
  1. isVoiceAgentEnabled — the voice agent feature flag is on for this inbox.
  2. provider === 'elevenlabs' — the inbox is configured with the ElevenLabs provider.
  3. agentId — a valid agent ID was resolved from the inbox config.
When any condition is false the button is not rendered, so the input area looks identical to a standard Chatwoot widget. This means there is no code change required to hide the button on inboxes without voice configured.
The voice button has a single mount point in ChatInputWrap.vue. An earlier version also mounted it in HeaderActions.vue; that instance was removed to ensure only one call button appears per widget.

Pre-chat form

custom/widget/views/PreChatForm.vue controls the flow before a conversation is created. It collects visitor name, email, phone number, and an initial message, then dispatches them to the server in a specific order:
  1. The contact record is updated with name and email before the conversation is created, so the AI greeting uses the name just entered rather than a cached name from a previous session.
  2. conversation/createConversation is dispatched with the full set of form fields.
  3. On the ON_CONVERSATION_CREATED event the router navigates to the messages view.
The form fields and their validation messages are defined in custom/widget/i18n/en.json under the PRE_CHAT_FORM.FIELDS key:
"PRE_CHAT_FORM": {
  "FIELDS": {
    "FULL_NAME": {
      "LABEL": "Full Name",
      "PLACEHOLDER": "Please enter your full name",
      "REQUIRED_ERROR": "Full Name is required"
    },
    "EMAIL_ADDRESS": {
      "LABEL": "Email Address",
      "PLACEHOLDER": "Please enter your email address",
      "REQUIRED_ERROR": "Email Address is required",
      "VALID_ERROR": "Please enter a valid email address"
    },
    "PHONE_NUMBER": {
      "LABEL": "Phone Number",
      "PLACEHOLDER": "Please enter your phone number",
      "REQUIRED_ERROR": "Phone Number is required"
    },
    "MESSAGE": {
      "LABEL": "Message",
      "PLACEHOLDER": "Please enter your message",
      "ERROR": "Message too short"
    }
  }
}
Which fields appear on the form is controlled by the inbox’s pre-chat form settings in the dashboard (Settings → Inboxes → Configuration → Pre Chat Form), not by the Vue component directly.

i18n labels

All visible widget text is sourced from custom/widget/i18n/en.json. The keys below are the ones most likely to need customisation:
KeyDefault valuePurpose
POWERED_BY"Powered by Visual Graphx"Footer branding text
BUBBLE.LABEL"Chat with us"Screen-reader label on the chat bubble
CHAT_PLACEHOLDER"chat with us"Placeholder text in the message input
START_CONVERSATION"Start Conversation"Button on the home view
TEAM_AVAILABILITY.ONLINE"We are online"Status shown when agents are online
VOICE_AGENT.START_CALL"Talk to AI"Tooltip / aria-label on the mic button
VOICE_AGENT.END_CALL"End Call"Label shown during an active voice call
VOICE_AGENT.CONNECTING"Connecting..."Transitional state label
VOICE_AGENT.MICROPHONE_ACCESS"Microphone access required. Please allow microphone permission."Error when mic permission is denied
To update any label, edit custom/widget/i18n/en.json, then run ./build.sh and rebuild the Docker image. Labels are inlined at build time by Vite and are not configurable at runtime.

Header actions

custom/widget/components/HeaderActions.vue renders the action buttons in the widget header. It provides:
  • Exit chat button — an icon button (door-with-arrow SVG) that appears when the conversation status is open, snoozed, or pending. Clicking it opens a confirmation popover with “Cancel” and “Exit Chat” options.
  • Popout button — opens the conversation in a new browser window. Only rendered when showPopoutButton is true.
The exit flow (endChat method) runs three steps in sequence:
  1. Calls toggleStatus to resolve the conversation server-side.
  2. Dispatches contacts/softExitChat to clear Vuex state and session storage without posting exitChat or reloading the iframe.
  3. Posts { event: 'closeWindow' } to the parent SDK so the iframe panel collapses.
The confirmation popover text is hardcoded in the component template:
<!-- custom/widget/components/HeaderActions.vue:152-157 -->
<p class="confirm-text">
  End and close this chat?
  <span class="confirm-sub">
    Conversation will be resolved.<br/>
    Next open will start fresh.
  </span>
</p>
To change this wording, edit HeaderActions.vue directly and rebuild.

Rebuild after changes

Changes to any file in custom/widget/ require a Vite rebuild and a Docker image rebuild before they are visible in the running widget. Running only docker compose restart rails is not sufficient — the widget bundle is compiled at image build time.
# Rebuild the widget bundle and Docker image
./build.sh

# Bring up the stack with the new image
docker compose up -d

Build docs developers (and LLMs) love