API Reference
Before your App code runs, Bruno injects a global window.bru into the guest page. All interaction with Bruno happens through bru.ctx.
Your App
│
bru.ctx
│
Bruno
Instead of using fetch() directly, your App calls bru.ctx.submitRequest() or bru.ctx.runRequest(). Bruno then interpolates variables, runs pre/post scripts, executes assertions and tests, records the timeline, and updates the response pane — exactly as if you pressed Send.
bru.ctx data arrives asynchronously after page load. Reading bru.ctx.http.response, bru.ctx.variables.resolved, or any other state field at the top level or inside DOMContentLoaded returns null or empty values.
Always do the first render inside bru.ctx.onInit. React to later changes via the on*Change callbacks.
Shared surface
These fields and methods are available in both request-level and collection-level Apps.
| Field | Type | Description |
|---|---|---|
bru.ctx.theme |
{ name, mode, config } |
mode is 'light' | 'dark', also mirrored as a light / dark class on document.body. config is the full resolved theme object (colors, tokens, etc.). |
bru.ctx.variables.resolved |
Record<string, any> |
Merged, read-only snapshot of all variables: global env → active env → collection vars → runtime vars (later keys win). |
bru.ctx.variables.runtime.set(name, value) |
function |
Persists a runtime variable on the active collection for the session. Same scope as bru.setVar in scripts. |
bru.ctx.log(...args) |
function |
Logs to the App's own console and forwards to Bruno's host console as [app] …. |
bru.ctx.onInit |
(bru) => void |
Assigned by your code. Called once when the initial state arrives. The argument is bru (not ctx) — access state via bru.ctx.* inside the callback. |
bru.ctx.onThemeChange |
(theme) => void |
Assigned by your code. Called when the theme changes. |
bru.ctx.onVariablesChange |
(variables) => void |
Assigned by your code. Called when any merged variable changes. |
Reading theme
// Check light/dark
const isDark = bru.ctx.theme.mode === 'dark';
// Read a specific token
const primaryColor = bru.ctx.theme.config.colors.primary;
// Or use the body class added automatically
document.body.classList.contains('dark'); // true in dark mode
Reading variables
bru.ctx.onInit = () => {
const baseUrl = bru.ctx.variables.resolved['baseUrl'];
console.log('Base URL:', baseUrl);
};
Setting a runtime variable
bru.ctx.variables.runtime.set('token', loginRes.data.token);
// Now {{token}} resolves in all subsequent requests
Request-level App
These fields are only available on request-level Apps. They do not exist on collection Apps.
| Field | Type | Description |
|---|---|---|
bru.ctx.http.response |
Response \| null |
The latest response for this request, from any source (Send button, runner, or the App itself). |
bru.ctx.http.onResponseChange |
(response) => void |
Assigned by your code. Fired whenever a new response arrives. |
bru.ctx.assertions |
Assertion[] |
Results of the latest assertion run for this request. |
bru.ctx.tests |
Test[] |
Results of the latest test run for this request. |
bru.ctx.onAssertionsChange |
(assertions) => void |
Assigned by your code. Fired when assertion results update. |
bru.ctx.onTestsChange |
(tests) => void |
Assigned by your code. Fired when test results update. |
bru.ctx.submitRequest(options?) |
(options?) => Promise<Response> |
Sends the parent request through the full Bruno pipeline. See Overrides. |
Sending the parent request
// Simple send
const res = await bru.ctx.submitRequest();
console.log(res.status, res.data);
// Send with runtime variable overrides
const res = await bru.ctx.submitRequest({
runtimeVariables: { userId: '42' }
});
The request must reference
{{userId}}in its URL, body, params, or headers. Bruno interpolates the variable — it does not search-and-replace literal strings.
Reacting to responses
bru.ctx.onInit = () => {
if (bru.ctx.http.response) render(bru.ctx.http.response);
};
bru.ctx.http.onResponseChange = (res) => render(res);
Reading test results
bru.ctx.onTestsChange = (tests) => {
tests.forEach(t => console.log(t.status, t.description));
// t.status is 'pass' or 'fail'
};
Collection-level App
These fields are only available on collection-level Apps. They do not exist on request Apps.
| Field | Type | Description |
|---|---|---|
bru.ctx.collection |
{ name, pathname } \| null |
Metadata for the collection the App belongs to. |
bru.ctx.onCollectionChange |
(collection) => void |
Assigned by your code. Fired when collection metadata changes. |
bru.ctx.listRequests() |
() => Promise<RequestSummary[]> |
Returns every HTTP/GraphQL/gRPC/WebSocket request in the collection. Folder Apps also see the whole collection, not just their folder. |
bru.ctx.runRequest(pathname, options?) |
(pathname, options?) => Promise<Response> |
Runs any listed request by its pathname. See Overrides. |
Listing requests
bru.ctx.onInit = async () => {
const requests = await bru.ctx.listRequests();
requests.forEach(r => console.log(r.method, r.name, r.pathname));
};
Running a specific request
const requests = await bru.ctx.listRequests();
const loginReq = requests.find(r => r.name === 'Login');
const res = await bru.ctx.runRequest(loginReq.pathname, {
runtimeVariables: { email: 'user@example.com', password: 'secret' }
});
Types
Response
type Response = {
status: number | null,
statusText: string | null,
data: any, // parsed JSON body, or raw string
headers: Record<string, string> | null,
duration: number | null, // milliseconds
size: number | null // bytes
}
RequestSummary
type RequestSummary = {
uid: string,
name: string,
pathname: string, // absolute filesystem path — stable unless the file is moved
type: string, // 'http' | 'graphql' | 'grpc' | 'ws'
method: string, // 'GET' | 'POST' | … (empty for non-HTTP types)
url: string
}
Assertion
type Assertion = {
uid: string,
name: string,
description: string,
status: 'pass' | 'fail',
error?: string
}
Test
type Test = {
uid: string,
description: string,
status: 'pass' | 'fail',
error?: string
}
Overrides
Both submitRequest and runRequest accept an optional options object:
{
runtimeVariables: {
key: 'value',
// …
}
}
How overrides work:
- Values are injected as ephemeral runtime variables for that single send only.
- They take precedence over all other variable scopes (global env, active env, collection vars, persistent runtime vars).
- The request must reference them using
{{name}}syntax — Bruno interpolates the variable at runtime. Hard-coded literal strings in the request body are not matched or replaced. - Overrides do not persist. To persist a value across requests, use
bru.ctx.variables.runtime.set(name, value).
Tips
bru.ctx.theme is an object, not a string.
Use bru.ctx.theme.mode for the light/dark switch. The mode is also mirrored as a light / dark class on document.body.
onInit receives bru, not ctx.
The callback signature is (bru) => void. Access all state as bru.ctx.* inside it.
// Correct
bru.ctx.onInit = (bru) => {
console.log(bru.ctx.variables.resolved);
};
// Also works (bru is the same global)
bru.ctx.onInit = () => {
console.log(bru.ctx.variables.resolved);
};
Overrides are nested under runtimeVariables.
Flat top-level keys on the options object are ignored.
// Correct
bru.ctx.submitRequest({ runtimeVariables: { id: '1' } });
// Wrong — id is silently ignored
bru.ctx.submitRequest({ id: '1' });
Pathnames are absolute filesystem paths.
listRequests returns paths like /Users/me/collections/myapi/users/get-user.bru. They are stable as long as the file is not moved or renamed.
No bru.ctx.http or bru.ctx.submitRequest on collection Apps.
Those fields are request-level only. Accessing them on a collection App returns undefined.
No bru.ctx.listRequests or bru.ctx.runRequest on request Apps.
Those methods are collection-level only.
Variable precedence.
bru.ctx.variables.runtime.set and runtimeVariables overrides both write to the runtime scope, which sits at the top of Bruno's precedence chain: global env → active env → collection vars → runtime vars.