Widgets
Widgets are custom workspace pages that run in a sandboxed iframe. Content is either hosted HTML (with window.vt injected) or an external URL that uses the same protocol. Bind queries, functions, agents, workflows, and blocks — then call them from the iframe without exposing workspace credentials.
Use them for interactive dashboards, ops consoles, and custom forms on the home grid or as sidebar pages. These are not chat widgets (Liquid UIs inside chat messages).
When to use
- Build custom workspace pages that need live table data or long-running jobs
- Reuse the same queries, functions, agents, and workflows from the browser
- Place the UI on the dashboard and/or as one or more sidebar pages
The iframe never gets raw API keys. Only keys listed under Bound resources and Jobs are callable through vt.
Create a widget
Open Build → Widgets and click New.
- Enter a Name and optional Description.
- Click create — Vettero creates a Hosted widget and opens the editor.
On the list, filter by All, Hosted, or URL. Widgets are workspace-only; there is no agent-scoped iframe widget panel.
Configure the widget
From the widget detail page, click Edit.
| Section | What you configure |
|---|---|
| Name / Description | Identity in lists and Studio |
| Source | Hosted HTML or External URL |
| HTML document or URL | Full HTML when hosted; required URL when external |
| Input args schema | JSON Schema for instance args → vt.getArgs() |
| Bound resources | One-shot calls via vt.run* |
| Jobs | Long-running runs via vt.startJob / watch / cancel |
| Preview | Live preview of the widget |
For hosted widgets, the vt SDK is injected before </body>. New hosted widgets without custom HTML get a default document that demos theme helpers and vt.getArgs().
Add sidebar integrations
On the widget detail page, manage Sidebar integrations. Each integration is a sidebar page with its own args and access control.
| Setting | What it does |
|---|---|
| Name | Label in the sidebar |
| Icon | Optional icon |
| Access roles | Who can open it; empty = all roles |
| Input args | Values matching the widget’s input schema |
With no integrations, the widget is not listed in the sidebar pool. You can still pin it on the Home dashboard. Dashboard tiles and integrations both supply instance args for vt.getArgs().
You can also place widgets under Workspace Settings → Sidebar Items (type Widget).
Bind resources and jobs
Bound resources (one-shot)
Same pattern as functions: unique keys, then call with vt.runQuery, vt.runFunction, vt.runWorkflow, vt.runAgent, or vt.runBlock.
| Type | Notes |
|---|---|
| Query | Any workspace query |
| Function | Any workspace function |
| Agent / Workflow | Must use the general channel |
| Block | Tool-capable block; pick a Service when needed |
runAgent / runWorkflow wait for completion (up to about 5 minutes) and throw if the run fails or is waiting for human approval. runBlock does not support human-in-the-loop interrupts.
Jobs (async)
Jobs are for long-running work. Add a key, type (Workflow, Agent, or Function), target, and scope:
| Scope | Meaning |
|---|---|
| Per user | One active run per caller |
| Global | One shared active run per job key |
Starting a job while one is already active for that scope fails with Job already running. Status values: pending, running, finished, failed, cancelled. Jobs cannot bind Query or Block.
Match the workspace theme
Hosted widgets receive the current workspace theme as CSS variables, including the company primary color and dark mode. Use the built-in classes for cards and controls. Use the variables when you need custom layout. Do not hardcode colors.
| Class | What it is |
|---|---|
vt-grid | Auto-fit card grid |
vt-card | Bordered panel |
vt-label / vt-value | Metric label and value |
vt-muted | Secondary text |
vt-btn / vt-btn-primary | Buttons |
vt-input | Text input |
Variables include --vt-bg, --vt-surface, --vt-surface-muted, --vt-border, --vt-text, --vt-text-muted, --vt-primary, --vt-primary-contrast, --vt-radius, and --vt-space. They update when the theme changes. vt.getTokens() returns the same values if a script needs them.
<div class="vt-grid">
<div class="vt-card">
<div class="vt-label">Total balance</div>
<div class="vt-value">—</div>
</div>
</div>
Use the widget SDK
Hosted widgets get window.vt automatically. URL widgets must speak the same vettero-widget postMessage protocol (or load the same SDK).
| Method | What it does |
|---|---|
vt.getArgs() | Instance args from the dashboard tile or sidebar integration |
vt.isDark() / vt.getTokens() / vt.onThemeChange(cb) | Theme flag, current design tokens, and a listener cb(dark, tokens) |
await vt.getUser() | Current user profile |
await vt.runQuery / runFunction / runWorkflow / runAgent / runBlock | Bound resources |
await vt.watchQuery(key, input?, onData) | Re-runs when related table rows change |
await vt.startJob / getJob / getCurrentJob / listJobs / cancelJob | Job lifecycle |
await vt.watchJob(key, runId?, onUpdate) | Watch a job; omit runId to watch the current run |
vt.runQuery to run a bound query. Add vt.watchQuery to watch for related table-row changes and make a live widget.<script>
async function load() {
const args = vt.getArgs()
const { rows, total } = await vt.runQuery('orders', { status: args.status })
const job = await vt.startJob('sync', { status: args.status })
console.log(total, job.status)
}
load()
</script>
Place on the home dashboard
On Home, use Add widget / Manage widgets to pin widgets as tiles. Each tile can pass its own args matching the input schema.
Limits
- Agents and workflows bound as resources or jobs must use the general channel.
- Jobs cannot bind query or block types.
- A second
startJobwhile an active run exists for that scope fails.
Where widgets are used
| Place | How |
|---|---|
| Home dashboard | Tile with the widget and optional args |
| Sidebar | Each Sidebar integration, or Settings → Sidebar Items |