Skip to content

7. Pass context

An agent that knows the visitor is on the invoices page, on the pro plan, looking at invoice INV-2041, answers a different question from one that only sees “why was I charged twice”. Vars are how you tell it.

Vars are a JSON object stored on the conversation thread and made available to the agent’s code as meta.vars. Three layers merge, in this order:

  1. Agent variables, from the Security tab. The same for every conversation.
  2. Widget vars, from your page. Per visitor and per conversation, and they override the agent’s values.
  3. Canonical keys, injected by Urai: thread_id, organization_id, and user_id. These always win.

Inside a script the agent writes, they read as meta.vars.plan or meta.vars.organization_id. The agent’s prompt lists the variable names it can expect, so anything you pass consistently is worth naming in the instructions too.

Keep vars small and non-secret. They travel from the browser and are stored on the thread, so a visitor can read them and can change them. Nothing here may decide what the agent is allowed to do. When the agent needs to call your API on the user’s behalf, pass a short-lived signed token your backend minted and verifies; see securing your app for the pattern.

The initial vars seed the first thread the visitor creates:

<UraiChatWidget
widgetToken="wgt_..."
userId={currentUser.id}
vars={{ plan: currentUser.plan, locale: "en-US", page: location.pathname }}
/>

With the script tag, use data-vars with a JSON object:

<script
src="https://chat.app.urai.dev/api/widget/embed.js"
data-widget-token="wgt_..."
data-user-id="user_42"
data-vars='{"plan":"pro","locale":"en-US"}'
></script>

In React, changing the vars prop calls setVars on the active thread. The prop is deep compared, so an inline object literal is fine:

function App() {
const location = useLocation();
return (
<UraiChatWidget
widgetToken="wgt_..."
userId={currentUser.id}
vars={{ plan: currentUser.plan, page: location.pathname }}
/>
);
}

Imperatively, through the controller or the global:

// Update the active thread, or buffer for the next one if none exists yet
UraiChat.setVars({ page: location.pathname, record_id: recordId });
// Clear
UraiChat.setVars(null);

setVars replaces the widget vars rather than merging into them, so send the whole object you want the thread to have.

When the visitor signs in, signs out, or switches account, set the user rather than the vars. Changing the id resets the conversation, which is what you want: the previous visitor’s thread should not be visible to the next one.

UraiChat.setUser({
id: currentUser.id,
vars: { plan: currentUser.plan, locale: currentUser.locale },
});

In React, the same thing happens by changing the userId prop.

Sometimes the context changes enough that continuing the old thread is wrong, such as when the visitor opens a different record. Threads are created lazily on the first message, so starting one on every route change costs nothing:

UraiChat.startConversation({ page: location.pathname, order_id: orderId });

Set identity once at sign-in, keep a small set of vars current on navigation, and start a fresh conversation only when the subject changes:

useEffect(() => {
if (!user) return;
widget.current?.setUser({ id: user.id, vars: { plan: user.plan } });
}, [user?.id]);
useEffect(() => {
widget.current?.setVars({ plan: user.plan, page: location.pathname });
}, [location.pathname]);

Then use them in your library and your instructions:

The user's current page is in `meta.vars.page` and their plan in
`meta.vars.plan`. On `/billing`, assume the question is about a charge before
asking what they mean.

Open the widget on two different pages of your app and ask “what am I looking at”. The answers should differ. In the console thread you can read the code the agent ran, so confirm it picked up meta.vars.page rather than asking.

Next: handle commands.