SDK Reference
Who can use this feature?
- Available to all users on any plan.
- New to the SDK? Start with the SDK overview.
The SDK is the global object window.$chatty. This page lists everything it exposes.
| Global object | window.$chatty |
| SDK version | 1.1.0 — read at runtime with $chatty.version |
| Install | Ships with the Chatty widget. Nothing extra to add. |
Methods
| Method | Returns | Description |
|---|---|---|
push([verb, action, ...args]) | — | Run a command. Verbs: do, set, on, off. |
on(event, callback) | — | Subscribe to an event. |
off(event, callback) | — | Unsubscribe. Pass the same function reference used in on. |
is(key) | boolean | Synchronously read a boolean state (see States). |
get(key) | value | Synchronously read a value (see Values). |
version | string | SDK version, e.g. '1.1.0'. |
on and off are also available as push verbs — push(['on', event, cb]) is equivalent to on(event, cb).
Commands pushed before the widget bundle loads are buffered and replayed in order once it is ready, so you never need to poll for readiness.
window.$chatty = window.$chatty || [];
window.$chatty.push(['do', 'chat:open'])do actions
Run with push(['do', action, ...args]).
Widget visibility
| Action | Arguments | Description |
|---|---|---|
chat:open | — | Open the chat window. |
chat:close | — | Close the chat window, back to the launcher. |
chat:toggle | — | Toggle open and closed. |
chat:show | — | Show the widget again after chat:hide. |
chat:hide | — | Hide the whole widget — launcher and window. |
launcher:show | — | Show only the launcher button. |
launcher:hide | — | Hide only the launcher button. |
window.$chatty.push(['do', 'chat:open'])
window.$chatty.push(['do', 'launcher:hide'])Messaging
| Action | Arguments | Description |
|---|---|---|
message:send | 'text', content | Send a message as the visitor. |
typing:set | boolean | Show or clear the visitor's typing indicator. |
window.$chatty.push(['do', 'message:send', 'text', 'I need help with my order'])
window.$chatty.push(['do', 'typing:set', true])The first argument of message:send is the message type and must be 'text'. The SDK cannot send attachments or rich content.
If a required pre-chat form is on screen, message:send is held and delivered as the first message as soon as the form is submitted.
Identity and session
| Action | Arguments | Description |
|---|---|---|
identify | object | Set visitor identity in one call. Every field is optional, but pass at least one. |
logout | — | Clear the identity and start fresh. Use it when the shopper logs out of your store. |
session:reset | — | End the current conversation and start a new one. Keeps the identity. |
window.$chatty.push(['do', 'identify', {
email: 'jane@example.com',
name: 'Jane Doe',
phone: '+15550134',
attributes: { plan: 'gold' }
}])identify is a shortcut for the matching set user:* actions. Both merge into the same visitor record, so use whichever fits your code — identify for a single call at page load, set when values arrive at different times.
Identity passed from the browser is supplied by the page, not verified by Chatty. Treat it as a convenience for your support team, not as proof of who the visitor is. Do not use it to gate anything sensitive. See Security and trust boundary.
Conversation control
| Action | Arguments | Description |
|---|---|---|
escalate | — | Ask for a human agent — the AI to human handoff. |
trigger:run | campaignId | Run a chat campaign by ID. |
article:show | articleId | Open an FAQ article inside the widget. |
window.$chatty.push(['do', 'escalate'])
window.$chatty.push(['do', 'article:show', articleId])Pre-chat form
Available from SDK 1.1.0.
| Action | Arguments | Description |
|---|---|---|
prechat:submit | — | Submit the form. Required fields are validated first. |
prechat:skip | — | Skip the form and chat anonymously, when the shop allows skipping. |
if (window.$chatty.is('prechat:visible') && !window.$chatty.is('prechat:required')) {
window.$chatty.push(['do', 'prechat:skip'])
}Prefill the fields with set prechat:field before calling prechat:submit.
Custom event tracking
| Action | Arguments | Description |
|---|---|---|
event:track | name, data? | Feed a named event into the widget context. |
window.$chatty.push(['do', 'event:track', 'viewed_size_guide', { product: 'shirt-101' }])Every tracked event is also re-dispatched on the page as a DOM CustomEvent named chatty:event, so other scripts can consume it without touching the SDK:
document.addEventListener('chatty:event', (e) => {
console.log(e.detail.name, e.detail.data) // 'viewed_size_guide', { product: 'shirt-101' }
})set actions
Run with push(['set', action, value]).
Composer and visitor data
| Action | Value | Description |
|---|---|---|
message:text | string | Prefill the input box without sending. The visitor can still edit it. |
user:email | string | Visitor email. Validated. |
user:name | string | Visitor name. |
user:phone | string | Visitor phone. |
user:attributes | object | Structured attributes about the visitor. |
user:context | object | Free-form context shown to your team and the AI. Merges across calls. |
shop:data | object | Free-form context about the shop or page. Merges across calls. |
window.$chatty.push(['set', 'message:text', 'I want to know about shipping'])
window.$chatty.push(['set', 'user:email', 'jane@example.com'])
window.$chatty.push(['set', 'user:context', { vipLevel: 3, accountAge: '2 years' }])user:context and shop:data merge rather than replace, so you can add keys as the page learns more. Values must be a string, number, or boolean.
Commerce context
Give the AI assistant structured context about what the visitor is looking at.
| Action | Value | Description |
|---|---|---|
product | object | The product currently being viewed. |
cart | object | The current cart. |
order | object | The order being discussed. |
window.$chatty.push(['set', 'product', { id: 123, title: 'Steel Door', price: '499.00' }])
window.$chatty.push(['set', 'cart', { items: 2, total: '648.00' }])
window.$chatty.push(['set', 'order', { name: '#1042', status: 'shipped' }])Read the merged result back with $chatty.get('commerce:context').
Locale
| Action | Value | Description |
|---|---|---|
locale | string | Language hint for the conversation. |
window.$chatty.push(['set', 'locale', 'de'])Pre-chat prefill
Available from SDK 1.1.0. Takes a field name and a value, so this is the one set action with two arguments.
window.$chatty.push(['set', 'prechat:field', 'name', 'Jane Doe'])
window.$chatty.push(['set', 'prechat:field', 'email', 'jane@example.com'])
window.$chatty.push(['set', 'prechat:field', 'phone', '+15550134'])
// 'message' is queued and sent automatically once the form is submitted
window.$chatty.push(['set', 'prechat:field', 'message', 'Where is my order #1042?'])Prefills are sticky — they apply even if the form appears later in the session.
Events
Subscribe with on(event, callback). The callback receives one data object, or nothing where the payload column shows —.
Lifecycle
| Event | Payload | Fires when |
|---|---|---|
sdk:ready | — | The SDK has initialised. Subscribing after it fired runs your callback immediately. |
chat:opened | — | The chat window opens, whether by the visitor or via the SDK. |
chat:closed | — | The chat window closes. |
Messages
| Event | Payload | Fires when |
|---|---|---|
message:sent | { text, type } | The visitor sends a message. |
message:received | { text, isAdmin, isAutomated } | A reply arrives, from a human agent or the AI. |
ai:reply | same as message:received | A reply from the AI assistant arrives. |
human:reply | same as message:received | A reply from a human agent arrives. |
message:updated | the updated message | An existing message is edited. |
message:removed | { id } | A message is removed. |
Conversation
| Event | Payload | Fires when |
|---|---|---|
conversation:started | { conversationId } | A conversation is created — that is, the visitor's first message was submitted. |
conversation:resolved | { conversationId } | The conversation is marked resolved. |
conversation:removed | { conversationId } | The conversation is deleted. |
handoff | { conversationId } | The conversation is handed from the AI to a human agent. |
Pre-chat and lead capture
| Event | Payload | Fires when |
|---|---|---|
prechat:shown | — | The pre-chat form is displayed. |
prechat:submitted | { conversationId } | The visitor submits the pre-chat form. |
email:captured | { email } | The visitor gives their email in the pre-chat form. |
survey:submitted | { rating, surveyComment } | The visitor submits a satisfaction survey. |
Presence and typing
| Event | Payload | Fires when |
|---|---|---|
agent:available | { online: true } | Support comes online — chat hours start. |
agent:unavailable | { online: false } | Support goes offline. |
agent:typing | { active } | A human agent starts or stops typing. |
ai:typing | { active } | The AI assistant starts or stops typing. |
unread:changed | { count } | The visitor's unread count changes. |
Avoiding duplicate handling
ai:reply and human:reply cover the same replies that message:received reports, split by sender. Subscribe to either message:received or the ai:reply / human:reply pair — not both — or a single reply runs your handler twice. This matters most for analytics, where it doubles your counts.
// Either this — one handler, branch on the payload
window.$chatty.on('message:received', ({ isAdmin }) => { /* ... */ })
// Or this — two handlers, no branching. Not both.
window.$chatty.on('ai:reply', () => { /* ... */ })
window.$chatty.on('human:reply', () => { /* ... */ })Unsubscribing
Pass the same function reference you subscribed with. Anonymous inline functions cannot be removed.
function onOpen() { /* ... */ }
window.$chatty.on('chat:opened', onOpen)
window.$chatty.off('chat:opened', onOpen)States
Read synchronously with is(key) → boolean.
| Key | True when |
|---|---|
chat:opened | The chat window is open. |
chat:closed | The chat window is closed. |
chat:visible | The widget is visible — it has not been hidden with chat:hide. |
session:ongoing | There is an active conversation. |
prechat:visible | The pre-chat form is on screen. |
prechat:required | The pre-chat form must be completed before chatting. |
agent:online | Support is within chat hours. |
if (window.$chatty.is('chat:opened')) {
window.$chatty.push(['do', 'chat:close'])
}Values
Read synchronously with get(key).
| Key | Returns |
|---|---|
message:text | Current text in the composer. |
session:identifier | The conversation ID, or null when no conversation is open. |
chat:unread:count | Unread messages for the visitor. |
prechat:config | { mode, preChatFields, requiredName, requiredEmail, required } |
commerce:context | The merged product, cart, and order objects. |
const unread = window.$chatty.get('chat:unread:count')Limits and validation
The SDK enforces limits to keep the widget responsive and to prevent abuse.
| Input | Limit |
|---|---|
message:send | 15 per minute, 1 second apart, ≤ 5,000 characters, no HTML |
typing:set | 30 calls per minute |
event:track | name ≤ 200 chars; data ≤ 20 keys, ≤ 1,000 chars per value |
user:email | valid email, ≤ 254 chars |
user:name | ≤ 150 chars, no HTML |
user:phone | ≤ 50 chars, no HTML |
user:context / shop:data | ≤ 50 keys, ≤ 2,000 chars per value, string / number / boolean only |
product / cart / order | plain JSON, ≤ 8 KB each |
locale | ≤ 35 chars |
Error handling
Commands never throw. A command that fails validation or hits a rate limit is dropped, and the SDK logs a warning prefixed with [ChattySDK] to the browser console.
window.$chatty.push(['set', 'user:email', 'not-an-email'])
// [ChattySDK] ... — the command is ignored, execution continuesTwo consequences worth designing around:
- There is no error callback and no return value. Your code cannot detect a rejected command at runtime, so validate input on your side before pushing it — especially anything coming from a form or a URL parameter.
- A dropped command is silent to the visitor. If a rate limit swallows a
message:send, nothing appears in the chat. Throttle on your side rather than relying on the SDK to queue.
Security and trust boundary
The SDK runs entirely in the visitor's browser. Everything below follows from that.
Identity is not verified. identify and the set user:* actions take whatever the page gives them. Anyone can open the browser console and pass another person's email. Chatty uses these values to label the conversation for your team, so:
- Do not use SDK identity to unlock account data, order details, or anything else you would gate behind a login.
- Do not treat a matching email in your inbox as proof of identity.
Input is sanitised. All text is sanitised before it reaches the widget. HTML and script content is rejected, and the fields marked "no HTML" above reject markup outright.
Only documented keys are exposed. is and get return the keys listed on this page. The SDK object does not expose the widget's internal state, and its methods are frozen so ordinary scripts on the page cannot replace them by accident. This is a guard against collisions with other scripts, not a defence against a hostile script — any script on your storefront runs with the same privileges as your own code.
Anything you push is visible to the visitor. user:context, shop:data, and the commerce objects sit in page memory. Pass what your support team needs to help, and nothing more — no internal costs, margins, risk scores, or notes you would not show the customer.
Analytics and GA4
Send Chatty activity to Google Analytics 4. Add this after your GA4 snippet, for example in theme.liquid.
<script>
window.$chatty = window.$chatty || [];
window.$chatty.push(['on', 'sdk:ready', function () {
// Chat window opened
window.$chatty.on('chat:opened', function () {
gtag('event', 'chatty_chat_opened');
});
// First message submitted — a new conversation exists
window.$chatty.on('conversation:started', function (data) {
gtag('event', 'chatty_conversation_started', {
conversation_id: data.conversationId
});
});
// Every message the visitor sends
window.$chatty.on('message:sent', function () {
gtag('event', 'chatty_message_sent');
});
// Pre-chat form submitted
window.$chatty.on('prechat:submitted', function (data) {
gtag('event', 'chatty_lead_submitted', {
conversation_id: data.conversationId
});
});
// Email captured in the pre-chat form
window.$chatty.on('email:captured', function () {
gtag('event', 'chatty_email_captured');
});
}]);
</script>Then mark one of them as a conversion in the GA4 admin.
Pick one conversion event. On a shop with a required pre-chat form, prechat:submitted and conversation:started both fire for the same visitor, so marking both counts the lead twice.
Migrating from another chat widget? The usual callbacks map like this:
| Typical callback | Chatty equivalent |
|---|---|
onOpen | on('chat:opened', …) |
onClose | on('chat:closed', …) |
onMessage outbound | on('message:sent', …) |
onMessage inbound | on('message:received', …) |
onLeadSubmitted | on('prechat:submitted', …) or on('conversation:started', …) |
Recipes
Custom "Chat with us" button
<button onclick="window.$chatty.push(['do', 'chat:open'])">Chat with us</button>Open chat with a question already typed
<button onclick="
window.$chatty.push(['do', 'chat:open']);
window.$chatty.push(['set', 'message:text', 'What is your returns policy?']);
">Ask about returns</button>One-click support with the message already sent
<button onclick="
window.$chatty.push(['do', 'chat:open']);
window.$chatty.push(['do', 'message:send', 'text', 'I need help with my recent order']);
">Get order help</button>Hide the widget on specific pages
if (window.location.pathname.startsWith('/pages/legal')) {
window.$chatty.push(['do', 'chat:hide'])
}Identify logged-in customers (Shopify Liquid)
{% if customer %}
<script>
window.$chatty = window.$chatty || [];
window.$chatty.push(['do', 'identify', {
email: {{ customer.email | json }},
name: {{ customer.name | json }}
}]);
</script>
{% endif %}Hold the widget until the visitor accepts cookies
Useful where consent is required before loading third-party chat.
window.$chatty = window.$chatty || [];
window.$chatty.push(['do', 'chat:hide'])
// Call this from your consent banner's "accept" handler
function onConsentGiven() {
window.$chatty.push(['do', 'chat:show'])
}What the SDK cannot do
- Read past conversations or message history — there is no getter for it.
- Send attachments, images, or rich content.
message:sendis text only. - Reply as an agent, or act on behalf of your team.
- Embed the widget. The SDK controls a widget that Chatty already loaded; it is not an npm package.
For server-side access to conversations, see the Chat Conversations API.
Browser support
Chrome 60+, Firefox 55+, Safari 12+, Edge 79+. The SDK uses ES6 and does not ship a transpiled fallback for older browsers.
Changelog
| Version | Changes |
|---|---|
| 1.1.0 | Added pre-chat form control — set prechat:field, do prechat:submit, do prechat:skip, and the prechat:visible / prechat:required states with get prechat:config. |
| 1.0.0 | Initial release. |
Check the version at runtime before calling a newer API:
window.$chatty.on('sdk:ready', () => {
const [major, minor] = window.$chatty.version.split('.').map(Number)
if (major > 1 || (major === 1 && minor >= 1)) {
window.$chatty.push(['set', 'prechat:field', 'email', 'jane@example.com'])
}
})Compare the parts as numbers, not the whole string — '1.9.0' >= '1.10.0' is true in a string comparison and wrong.
Troubleshooting
window.$chatty is undefined. The widget script has not loaded on that page. Check that the Chatty app embed is enabled in your theme and that the page is not excluded. Starting your code with window.$chatty = window.$chatty || [] makes it safe regardless of load order.
chat:open does nothing. The widget may still be initialising — wait for sdk:ready — or it was hidden earlier by chat:hide, in which case call chat:show first.
message:send seems ignored. Check the console for a [ChattySDK] rate-limit or validation warning. If a required pre-chat form is open, the message is held and sent once the form is submitted.
A handler runs twice. You are probably subscribed to message:received and to ai:reply or human:reply at the same time. See Avoiding duplicate handling.
Events are not reaching GA4. Register listeners inside or after sdk:ready, and confirm the event fires at all from the console first:
window.$chatty.on('chat:opened', () => console.log('opened'))Need help?
If a command still is not working, confirm the widget is loading on the page — window.$chatty should be defined. Still stuck? Contact the Chatty support team from your dashboard.
Chatty Help Center