Usher for web

Beta
Last updated  Oct 5, 2026

Usher is an assistant that lives inside your signed-in React web app. Your users talk to it or type to it, and on every turn it decides whether to answer, offer a page, take them there, or ask a clarifying question. It moves them through your own router and only ever to pages you’ve listed.

A complete, step-by-step brief for Claude Code, Cursor, or any coding agent.
src/usher.tsx
import { buildDestinationMap } from "@voqal/usher-core";import { Usher } from "@voqal/usher-react";import { reactRouterAdapter } from "@voqal/usher-react-router";import { router } from "./router"; // your existing createBrowserRouter(...)const adapter = reactRouterAdapter(router);const destinations = buildDestinationMap({ discovered: adapter.discover() });export function UsherAssistant() {  return (    <Usher      voqalKey={import.meta.env.VITE_VOQAL_KEY}      router={adapter}      destinations={destinations}    />  );}

That’s a working integration: Usher discovers your routes from the router. The quickstart adds the key, the two lines that render it, and the optional layers that make it better.

What Usher does

  • A floating orb (bottom-right) starts a voice conversation. Its Aa button opens a chat panel. Voice and chat are one conversation with one context.
  • It knows your app through a destination map: an id, a route pattern, a title, a description, and the phrases people use for each page. The model picks an id from that list. It never produces a URL.
  • It can answer from a small knowledge base you pass in, and link an answer to the page it’s about.
  • It respects roles. A destination with roleScopes is hidden from users outside those roles. The model is never told it exists.
  • It can call your app’s own tools, like “create a draft invoice”, that you write in usher.tools.ts. They run in the browser as the signed-in user. Every key. See Tools.
DecisionExampleWhen
Navigate“Take me to billing”Imperative phrasing, one confident destination, and you aren't already on it.
Offer“How do I book a consult?”Answers, then offers to take you there. You confirm before anything moves.
Answer“What's your refund policy?”Nothing to navigate to, or you're already on the page. Replies in a sentence or two.
Clarify“Open settings”Two destinations are about equally likely. Asks one short question.
RefuseA page this user can't reachThe page doesn't exist or is out of role (the user isn't told which), or the target is unresolved, unsafe, or rejected by your router. It says so plainly. Nothing moves.

How a turn works

Your router adapter supplies the route tree. Your manifest adds titles, descriptions, aliases, and roles. buildDestinationMap merges the two into the destinations you pass to <Usher>. On each turn the model chooses a tool (navigate, offer_navigation or clarify) or simply replies. Before anything moves, a deterministic validation chain checks the choice: the id must be listed, the role must allow it, every dynamic param must come from a safe source, and the final path must be relative. Only then does Usher call your router, so your route guards still run.

The model is never trusted with ids. For a route like /cases/:caseId, the caseId comes from the selected entity in context, the current URL, or your own resolveEntity function. A value the model invents is ignored. See Dynamic params.

Packages

Install the React bindings, the core, and the one adapter that matches your router.

PackageInstallWhat it gives you
@voqal/usher-reactRequiredThe <Usher> component: the orb, the chat panel, voice audio, and the imperative handle.
@voqal/usher-coreRequiredTypes, buildDestinationMap, the validation chain, the policy, and the session runtime. Framework-agnostic.
@voqal/usher-react-routerOne adapterreactRouterAdapter(router) for React Router 6.4+ and 7 data routers. Discovers routes and navigates through router.navigate().
@voqal/usher-nextOne adapternextAdapter(router, options) for Next.js. Navigates with router.push(); you pass the route list.

Requirements

  • React 18 or newer (react and react-dom are peer dependencies).
  • A client-side router: React Router 6.4+ or 7 with a data router (createBrowserRouter), Next.js 13+, or your own adapter.
  • A publishable key from Voqal. It works from any website unless you ask Voqal to lock it to your domains.
  • ES modules. The packages ship ESM with type definitions and no CommonJS build.
  • For voice: a secure context (HTTPS or localhost), microphone permission, and AudioWorklet support in a current browser.

What’s in the beta

This is Usher 1.0.0 Beta. Install it with npm install @voqal/usher-react @voqal/usher-core @voqal/usher-react-router (swap the adapter for Next.js).

  • On every key: navigate, offer, answer (including from your knowledge base), clarify and refuse. Your app’s own tools. Questions about what’s on screen, answered by reading the page. Voice (hands-free or push-to-talk) and the chat panel on one session hosted by Voqal with your publishable key. Themes, role scoping, safe dynamic params, and the React Router and Next.js adapters.
  • The voice is set on Voqal’s side: Voqal’s default voice, in US English, today. The voice model, voice and language can change (for everyone, or for your key on request) with nothing to update in your app.
  • Powered by Voqal. A small “Powered by Voqal” line under the chat input is shown by default. It can be removed on request or on paid plans; it’s set on Voqal’s side, so there’s nothing to change in your code.
  • Not included: tabs and modals that have no URL. A dashboard for editing your map: the map and knowledge base live in your code.
  • The API can change between beta releases, so read the release notes before you upgrade. Update your privacy notice to say the assistant may read the current page.

How updates reach you

How the assistant behaves is set by Voqal’s hosted service, not by the package you installed. Each session Usher starts with your voqalKey gets Voqal’s current instructions: how it talks, when it offers a page instead of opening it, what counts as a yes, and how it recovers when the model goes quiet. When Voqal improves them, every session opened afterwards uses the new version. You don’t upgrade, rebuild, or redeploy.

  • Upgrade the package for new UI, new props, and new features. The release notes say when a version needs one.
  • Your layer stays yours. Your title, instructions, and knowledge are still added to every session, below Voqal’s rules. They can shape the tone and the facts, but they can’t switch off the safety rules.
  • The safety checks stay in the package. Whatever the instructions say, Usher only opens pages in your destination map, checks roles, and never takes an id from the model. Your router guards still run last.
  • It never breaks a session. If the hosted instructions can’t be loaded or read, Usher uses the version built into the package and carries on.
  • Self-hosting with your own voice.fetchGrant uses the built-in version, which changes only when you upgrade.

The code updates too

<Usher> is a small, stable loader. The assistant itself (the orb, the panel, the session, page reading and your tools’ runner) comes from Voqal when the page loads, so fixes to the browser code reach your app too, with no npm update. Your code doesn’t change: the props, tools and the ref stay the same.

  • Verified before it runs. The loader runs Voqal’s code only after checking two signatures against public keys in the package. One signature is on the release; the other, renewed automatically and good for 14 days at most, says it is current. It also checks the code’s hash, and then runs exactly those bytes.
  • Updates arrive automatically. Each page load gets Voqal’s current runtime, signed and verified as above before it runs. If it can’t be loaded or verified, the assistant doesn’t start, and the rest of your page is unaffected.
  • Your React. It uses your app’s React (18 or 19) and renders nothing on the server, so there’s no hydration mismatch.

Next steps

© 2026 VoqalVoqal SDK & engine documentation