message.comDevelopers

Build a custom chat launcher

Replace the default round launcher with your own button. Useful for marketing pages with a hero CTA, navbar entry points, or pricing pages where you want a Talk to sales link to open chat directly.

1. Hide the default launcher

Add the data-no-launcher attribute on the script tag. The bundle still loads and the chat panel still works; we just do not render the default button.

HTML
<script
  src="https://app.message.com/widget.js"
  async
  data-workspace="YOUR_WORKSPACE_ID"
  data-no-launcher="true"
></script>

2. Add your own button

Build any button you want. Hook its click handler to Message.toggle() (or open() / close() if you prefer one-way controls).

HTML
<button id="chat-cta" type="button">
  Talk to support
  <span id="chat-unread" hidden>0</span>
</button>

<script>
  const btn = document.getElementById("chat-cta");
  const badge = document.getElementById("chat-unread");

  btn.addEventListener("click", () => window.Message?.toggle());

  window.MessageOnReady = function () {
    window.Message.on("unread:changed", ({ count }) => {
      badge.textContent = String(count);
      badge.hidden = count === 0;
    });
  };
</script>

Message.toggle(), Message.open(), and Message.close() are part of the JavaScript API. Calling them before the bundle has loaded is a no-op; use MessageOnReady for handlers that depend on the API being ready.

3. Show the unread count

Subscribe to unread:changed. The widget emits this whenever new agent messages arrive while the panel is closed.

4. Animate state changes

Listen for open and close events to flip a class on your button:

javascript
window.Message.on("open", () => btn.classList.add("chat-open"));
window.Message.on("close", () => btn.classList.remove("chat-open"));

5. Open with a topic preset

Common pattern: a pricing-page button that opens chat and seeds the first message:

javascript
btn.addEventListener("click", () => {
  window.Message?.open();
  window.Message?.sendMessage("Hi, I have a question about pricing.");
});

Common pitfalls

  • Calling Message before ready. Use MessageOnReady or window.Message?.toggle() with the optional-chaining guard. Otherwise the click handler can fire before the bundle exists.
  • Multiple buttons. Several buttons can toggle the same panel. Message.toggle() is idempotent.
  • SPA route changes. If the button unmounts and remounts on navigation (React, Vue), rebind the click handler each mount.
  • Accessibility. Your custom button needs the same accessibility affordances the default has: focusable, aria-label, visible focus ring. The default launcher meets WCAG AA; replacements often regress.

Next steps