The plugin
A host plugin is what your app lends a surface: functions it may call, sources its data model may follow, and the policy that gates both. All three are plain options of ExponentialHost:
// A host plugin = what your app lends a surface: functions it may call,
// sources its data model may follow, and the policy that gates both.
import { ExponentialHost, decideUrl, parseSource } from "@exponential-at/ui"
import type { HostFunction, HostPolicy, SourceResolvers, Transport } from "@exponential-at/ui"
// 1. Functions: `on: {press: {functionCall: {call: "cart.add", args: {sku: "A1"}}}}`.
const functions: Record<string, HostFunction> = {
"app.toast": ({ text }) => console.info(String(text)),
"cart.add": async ({ sku }) => (await fetch(`/api/cart`, { method: `POST`, body: JSON.stringify({ sku }) })).ok,
}
// 2. Bindings: `bindDataModel {path: "/weather", source: "weather:current?city=Vienna"}`.
// The host parses the URI (parseSource) and calls the scheme's resolver.
const sources: SourceResolvers = {
weather: ({ name, params }, emit) => {
const poll = async () => emit(await (await fetch(`/api/weather/${name}?city=${encodeURIComponent(params.city ?? ``)}`)).json())
void poll()
const timer = setInterval(poll, 60_000)
return () => clearInterval(timer)
},
}
// 3. Policy: deny wins, then allow, then ask (your consent UI), then default.
const policy: HostPolicy = {
functions: { allow: [`app.*`], ask: [`cart.*`], default: `deny` },
onFunctionCall: (call) => window.confirm(`Allow ${call.name}(${JSON.stringify(call.args)})?`),
urls: { schemes: [`https`, `mailto`], hosts: [`example.com`, `*.example.com`] },
openUrl: (url) => window.open(url, `_blank`, `noopener`),
media: { baseUrl: location.origin, rules: [{ prefix: `${location.origin}/api/`, headers: { authorization: `Bearer ${sessionStorage.token ?? ``}` } }] },
}
export function createHost(transport: Transport): ExponentialHost {
return new ExponentialHost({ transport, functions, sources, policy })
}
// The same pure rules the host applies, callable on their own:
export const parsed = parseSource(`weather:current?city=Vienna`) // {scheme: "weather", name: "current", params: {city: "Vienna"}}
export const blocked = decideUrl(policy.urls, `https://elsewhere.test/`) // {allowed: false, reason: "host"}Functions
A surface calls a function with {"functionCall": {"call": "cart.add", "args": {…}}} on an event. The catalog's 31 functions are built in; any other name must be registered, and passes the gate first: deny wins, then allow, then ask (your onFunctionCall consent hook), then default. Patterns are exact names or prefixes ending in *. A function may be async; the button stays pending until it settles.
Data bindings
bindDataModel points a path at a source URI, <scheme>:<name>?k=v. The host parses it and calls your resolver for the scheme, which emits values whenever it likes (a poll, a socket, a local store) and returns a cancel function. Each emit lands at the bound path, and the surface repaints.
URL and media policy
openUrl and Link go through the URL policy: allowed schemes (default https http mailto tel), an optional host allowlist, relative URLs against a base. Media rules add headers to matching image requests, for example an authorization header for your own API and nothing for anyone else's.
The rules are the same on every platform: they are written once in host.json and replayed from fixtures by every renderer. The Exponential web app is a host built on exactly this API: harness.* functions, an exp: source scheme over its synced data, a consent card for ask.
Compiled in CI
Every file on this page is a real file in apps/ui-site/guides/react. CI copies it into an empty directory and builds it from scratch, the way you would, so the example cannot drift from the SDK:
bun apps/ui-site/guides/check.ts reactFor a complete app on this platform, see the samples: four hosts rendering the same surface from a local A2UI server, in a third-party theme, with a custom extension component.