Bridge API reference
The typed client your panel uses to talk to Chataway — context, files, host media, accounts, cards, badges and events — plus the postMessage protocol underneath.
npm install @chataway/appsimport { connect } from '@chataway/apps/bridge';
import '@chataway/apps/theme.css';
const app = await connect();@chataway/apps/bridge has no dependencies and works with any framework. Everything is promise-based; errors are BridgeErrors.
#Connecting
#connect(options?)
Handshakes with the host and resolves with a ChatawayApp. Calling it again returns the same connection.
| Option | Type | Default | |
|---|---|---|---|
mock | boolean | MockHostOptions | auto | true forces the mock host, false forbids it. Default: mock only when the page isn't embedded in Chataway. |
timeoutMs | number | 30000 | Default per-request timeout. Pickers, connect and media use longer ones. |
connectTimeoutMs | number | 10000 | How long to wait for the host's first answer. |
applyTheme | boolean | true | Apply the host theme to <html> (palette, light/dark, exact colours) and keep it updated. |
#isEmbedded()
true when the page runs inside a Chataway panel or card.
#Context
#app.context
What the host sent on connect. Updated automatically on view and theme events.
| Field | Type | |
|---|---|---|
pluginId | string | Your app id. |
projectId | string | The current project (a local id — not the pseudonym your server sees). |
chatId | string | null | The active chat, if any. |
cardId | string | null | Set when this page is a webview card. |
params | object | Card params (webview cards). |
view | string | null | The selected header view. |
apiBase | string | Path prefix for host URLs. files.url() and host.media() already apply it. |
theme | HostTheme | The host palette as hex: bg, surface, hover, text, muted, dim, border, primary, primaryFg, danger, success, dark, palette. |
#app.getContext()
Re-reads the context from the host. Promise<AppContext>.
#Files
#app.pickFiles(options?)
Opens Chataway's file picker: Media library, project files, or upload from the device. Resolves with only the files the user chose. Needs files:receive.
| Option | Type | |
|---|---|---|
accept | string[] | MIME types or extensions: ['image/*', '.pdf']. |
multiple | boolean | Allow several files. Default false. |
Returns Promise<PickedFile[]>:
PickedFile | |
|---|---|
ref | Opaque plugin-file: ref — pass it to host.media and files.*. |
name | File name. |
type | MIME type. |
size | Bytes. |
blob() | Promise<Blob> — the bytes. |
url() | Promise<string> — a URL you can use in <img>, <video> or fetch. |
Rejects with cancelled when the user closes the picker. Default timeout: 15 minutes.
const files = await app.pickFiles({ accept: ['image/png', 'image/jpeg'], multiple: true });
const form = new FormData();
for (const f of files) form.append('file', await f.blob(), f.name);
await fetch('https://api.example.com/upload', { method: 'POST', body: form, headers: { Authorization: `Bearer ${token}` } });#app.files.url(ref)
A URL for a ref your app may see (a picked file, a host media result, a file you returned). Promise<string>.
#app.files.addToLibrary(ref)
Copies a file your app owns into the project's Media library. Promise<{ ref }> — the library copy's ref. Needs files:return.
#Host media
#app.host.media(op, ref, params?)
Runs a host media operation on a file and returns a new file. Needs host:media.
const out = await app.host.media('resize', photo.ref, { width: 512, height: 512, fit: 'cover', format: 'png' });
// out = { ref, url, info? }| Result | |
|---|---|
ref | The new file's ref. The source is never modified. |
url | Loadable URL of the result. |
info | For probe: { width, height, duration, codecs }. |
Default timeout: 11 minutes (jobs queue, at most 2 per app at a time).
#Accounts
#app.accounts.list()
Your declared accounts and whether each is connected. Promise<AccountState[]>: { id, label, connected, expiresAt }.
#app.accounts.connect(id)
Starts the OAuth flow. Resolves with the account state once connected; rejects with cancelled if the user gives up. Must be called from a user gesture (a click handler).
#app.accounts.getToken(id)
A fresh access token for calling your own API from the panel. Promise<{ accessToken, expiresAt }>. The host refreshes it when needed — ask again rather than caching it. Rejects with denied if the account isn't connected.
#Cards and jobs
#app.listCards(options?)
Your app's cards in this project. options.status: pending · resolved · expired · cancelled · info. Promise<PluginCard[]>.
#app.respondCard(cardId, actionId, values?)
Answers one of your cards from the panel — for example, approving a pending upload from a queue view. values carries form answers. Promise<PluginCard>. Only your own cards; others reject with denied.
#app.listJobs() · app.cancelJob(jobId)
Your app's recent jobs in this project (for example host media jobs), and cancelling one that's running.
#UI
#app.setBadge(view, count)
Shows a count on one of your header views. 0 clears it.
#Events
const off = app.on('card', card => updateQueue(card));
off(); // unsubscribe| Event | Payload | When |
|---|---|---|
view | string | The user switched the header view. |
theme | HostTheme | Palette or light/dark changed. Applied to <html> automatically unless applyTheme: false. |
card | PluginCard | One of your cards was created or changed (answered, expired…). |
job | PluginJob | One of your jobs progressed or finished. |
app.off(event, handler) also unsubscribes. app.close() stops listening; pending requests reject with closed.
#Errors
Every failure is a BridgeError with a code and the method that failed:
code | Meaning |
|---|---|
cancelled | The user closed a picker or the connect flow. Usually not worth an error message. |
denied | A grant is missing, or it's not your card, job or file. |
timeout | No answer in time. |
unsupported | This version of Chataway doesn't know the method — ask the user to update. |
closed | app.close() was called. |
host | Anything else; see message. |
import { BridgeError } from '@chataway/apps/bridge';
try {
await app.pickFiles();
} catch (e) {
if (e instanceof BridgeError && e.code === 'cancelled') return;
throw e;
}#Mock host
Opened directly in a browser (npm run ui with Vite), connect() uses a mock host so you can build the UI without Chataway. A small floating toolbar switches palette, light/dark and view.
const app = await connect({
mock: {
views: [{ id: 'library', label: 'Library' }, { id: 'uploads', label: 'Uploads' }],
accounts: [{ id: 'acme', label: 'Acme', connected: true }],
files: [{ name: 'test.png', type: 'image/png', bytes: pngBytes }],
dark: false,
},
});MockHostOptions | |
|---|---|
pluginId, projectId, chatId | Context values. |
views, view | Header views and the selected one. |
palette, dark | Initial theme. |
accounts | Accounts to pretend are declared. |
cards, jobs | Initial cards and jobs. |
files | Files pickFiles returns without a chooser. |
toolbar | Show the dev toolbar (default true in a browser). |
latencyMs | Simulated latency. |
app.isMock is true against the mock host, and app.mockHost lets you emit events from your dev tools.
#Protocol
The client is a thin layer over postMessage. You don't need this unless you're writing your own client.
// page → host
{ type: 'claw:request', id: 'r1', method: 'pickFiles', params: { accept: ['image/*'], multiple: true } }
// host → page
{ type: 'claw:response', id: 'r1', result: [ /* files */ ] }
{ type: 'claw:response', id: 'r1', error: 'cancelled by the user' }
{ type: 'claw:event', event: 'theme', payload: { bg: '#0b0b0c', /* … */ } }Methods: getContext, pickFiles, host.media, accounts.list, accounts.connect, accounts.getToken, files.url, files.addToLibrary, listCards, respondCard, listJobs, cancelJob, setBadge. The page posts to window.parent with target '*' (the sandboxed frame has an opaque origin); the host only accepts messages from your frame.