Complete reference for the bru.ctx object available inside every Bruno App.
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.
// Check light/darkconst isDark = bru.ctx.theme.mode === 'dark';// Read a specific tokenconst primaryColor = bru.ctx.theme.config.colors.primary;// Or use the body class added automaticallydocument.body.classList.contains('dark'); // true in dark mode
// Simple sendconst res = await bru.ctx.submitRequest();console.log(res.status, res.data);// Send with runtime variable overridesconst 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.
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).
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.
// Correctbru.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.
// Correctbru.ctx.submitRequest({ runtimeVariables: { id: '1' } });// Wrong — id is silently ignoredbru.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.