The package
A vapp is data, never code: templates (components, initial data, bindings), the functions it may call and a theme, in one JSON package. This one shows a greeting that follows a weather source and can toast:
{
"id": "com.example.hello",
"name": "Hello",
"version": "1.0.0",
"description": "A greeting card that follows the weather source and can toast.",
"catalogId": "https://ui.exponential.at/catalogs/core/v1",
"theme": "neutral",
"functions": ["app.toast"],
"templates": {
"main": {
"components": [
{ "id": "root", "component": "Card", "padded": true, "children": ["title", "body", "wave"] },
{ "id": "title", "component": "Text", "text": { "path": "/title" }, "variant": "title" },
{ "id": "body", "component": "Text", "text": { "path": "/weather/summary" }, "variant": "muted" },
{ "id": "wave", "component": "Button", "label": "Wave", "on": { "press": { "functionCall": { "call": "app.toast", "args": { "text": "Hello back!" } } } } }
],
"data": { "title": "Hello, world", "weather": { "summary": "Loading the weather…" } },
"bindings": [{ "path": "/weather", "source": "weather:current?city=Vienna" }]
}
}
}Its functions list is the policy for its surfaces: listed names are allowed, everything else is denied, whatever the host allows elsewhere. theme is a built-in id or a whole theme JSON.
Run it
validatePackage lists every problem with a path; createVappHost installs the package in a host that lends it the functions and sources it names. open applies a template onto a surface, which you paint like any other:
// A declarative vapp = templates + data + bindings + the functions it may
// call. Data, never code: validate it, then run it in any host.
import { createVappHost, validatePackage } from "@exponential-at/ui"
import type { VappPackage } from "@exponential-at/ui"
import pkg from "./hello.vapp.json"
const issues = validatePackage(pkg)
if (issues.length) throw new Error(issues.map((i) => `${i.path}: ${i.message}`).join(`\n`))
export const vapp = createVappHost({
package: pkg as VappPackage,
// The host lends what the package names: the toast (its `functions` list
// narrows the policy) and the `weather:` source its binding follows.
functions: { "app.toast": ({ text }) => console.info(String(text)) },
sources: { weather: (_source, emit) => emit({ summary: `Sunny, 21 °C` }) },
})
vapp.open(`hello`) // applyTemplate: the first template onto surface "hello"
// Paint it: <HostSurface host={vapp.host} surfaceId="hello" theme="neutral" />Embed and connect
Because a package is data, the same file runs in every host: the web, the SwiftUI and Compose hosts, the gpui host. Against an Exponential instance, ExponentialConnector does the MCP OAuth grant (discovery, dynamic registration, PKCE) and serves the instance's issues, boards, teams and members as exp: sources, so a vapp can show live data the user has consented to. Hosted vapps, which run elsewhere and stream their UI, embed through the same Transport interface.
The Exponential app ships its Devices screen this way: a package of templates that its web app and desktop IDE both render.
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.