Client functionsAll pages

Client functions

Hand your support team a button that runs code in the visitor’s own browser, with the visitor’s own session, and posts the answer back into the conversation. It turns “can you tell me what is in your cart” into a click.

What this is#

Your page registers named functions with Connect. Only their descriptions travel — name, purpose, and the shape of their arguments. The functions themselves never leave your page.

An agent viewing that conversation sees the list in their sidebar, fills in a small form generated from your schema, and clicks run. The call goes down the visitor’s live connection, your handler executes in their browser, and the result comes back into the conversation where both of them can see it.

Registering one#

a function your agents can run
$connect.push(["register", "function", [
  {
    name: "get_cart",
    description: "Return the visitor's current cart contents.",
    parameters: {
      type: "object",
      properties: {
        currency: { type: "string", description: "ISO currency code" },
      },
    },
    handler: function (args) {
      return {
        items: cart.lines.length,
        total: cart.formattedTotal,
        currency: (args && args.currency) || "USD",
      };
    },
  },
]]);

Register whenever you like — before the widget loads is fine. The manifests are re-sent automatically if the chat window ever reloads, so a long session does not quietly lose them.

several at once
// Register everything in ONE call: the last call is the set your agents see.
$connect.push(["register", "function", [
  { name: "get_cart",       description: "…", parameters: {}, handler: getCart },
  { name: "get_order",      description: "…", parameters: {}, handler: getOrder },
  { name: "apply_discount", description: "…", parameters: {}, handler: applyDiscount },
]]);

The manifest#

namerequired · ≤ 64 chars · identifier
Letters, digits, and underscores, not starting with a digit — the same rule as a JavaScript identifier. Anything else is rejected. This is what the agent sees, so get_cart beats fn1.
descriptionrequired · ≤ 500 chars
Written for the person deciding whether to click it. Say what it does and what it changes — an agent will not read your source.
parametersJSON Schema object · defaults to {}
Drives the little argument form in the sidebar. Use { type: "object", properties: {…} }, name the required ones, and give each property a description — it becomes the field’s label. Omit it entirely for a function that takes nothing.
handlerrequired · function
Stays on your page. Never serialised, never sent, never seen by Connect. An entry without a callable handler is skipped silently.

Writing the handler#

It receives the arguments object the agent filled in, and may return a value or a promise. Async is normal.

an async handler that can fail
{
  name: "resend_receipt",
  description: "Email the receipt for an order to the signed-in customer.",
  parameters: {
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"],
  },
  handler: async function (args) {
    const res = await fetch("/api/orders/" + args.orderId + "/receipt", {
      method: "POST",
    });
    if (!res.ok) throw new Error("Could not resend receipt (" + res.status + ")");
    return { sent: true, to: currentUser.email };
  },
}
  • Return something readable. The result is rendered into the conversation for the agent — and the visitor can see the thread. A small object of plain values beats a raw API response.
  • Throw to report failure. The error message is what the agent sees, so make it say what went wrong.
  • Ten seconds. Longer than that and the call is recorded as a timeout. Kick off long work and return a reference rather than waiting on it.
  • Assume it can run twice. Nothing stops an agent clicking again. Make destructive handlers idempotent, or do not register them.

What happens on a call#

  1. 01

    The agent clicks run

    The arguments are validated against your schema and the call is recorded — who triggered it, on which conversation, with what arguments.

  2. 02

    It arrives in the visitor’s browser

    Down the live connection the widget already holds. Nothing new is opened and the visitor sees no prompt.

  3. 03

    Your handler runs

    In the visitor’s page, with their session and their cookies — which is the entire point. There is no server call impersonating them.

  4. 04

    The result posts back

    Success or failure, it lands in the conversation as a structured entry and the audit record is completed. An unknown function name comes back as an error rather than silence.

Limits#

Functions per visitor32
Beyond that, the extras are dropped.
Handler timeout10 seconds
Recorded as a timeout; a late return is discarded.
Result size16 KB
Return a summary and a link, not a dataset.
Registration payload64 KB
All manifests together. Descriptions are the usual culprit.
Lifetimethe widget session
Registrations are held in memory, per visitor, and are gone after a server restart. Your page re-registers on the next load, so this is invisible in practice — but do not treat a registration as durable.

Security#

  • Only an agent on that conversation can trigger a call. A visitor cannot.
  • Only names your page registered in this session can run. A call for anything else is refused.
  • Handlers never cross the boundary. Connect stores a name, a sentence, and a schema.
  • Every call and result is recorded against the conversation with the member who triggered it — who ran what, when, with which arguments, and what came back.

Things worth registering#

  • get_cart, get_order, get_subscription — read the state the visitor is describing badly.
  • get_client_diagnostics — app version, feature flags, last error, browser storage state. Ends most “it does not work” conversations.
  • resend_receipt, resend_verification_email — safe, idempotent, and the single most common request in support.
  • apply_discount with a fixed code — bounded, reversible, and it makes an agent look like they can actually help.
  • reload_app_state, clear_local_cache — the “try refreshing” advice, done for the visitor instead of explained to them.

A complete worked example#

The Use cases page has one wired end to end against a storefront, alongside the rest of the recipes.