Bruno provides a powerful visualization feature that allows you to display API response data in a more readable and interactive format using the bru.visualize function. This feature supports multiple providers and formats to help you analyze and present your API data effectively.
bru.visualize(type, config)
-
type(string): The type of visualization to render (e.g., ‘table’, ‘html’).
-
config(object): Depends on
type:
table: name, provider (ag-grid, react-table), props.
html (raw HTML): name, content — unchanged from earlier releases.
html (Handlebars): name, template (Handlebars string), data (object passed into the template), optional options.
Handlebars is compiled server-side; the Visualize tab receives rendered HTML plus rawData for Postman-style callbacks (see below).
Supported Visualization Types and Providers
Table Visualization (‘table’)
You can render tables using different providers like ag-grid and react-table.
Using ag-grid
Example:
const rowData = [
{ name: 'John Doe', age: 28, email: 'john@example.com', city: 'New York' },
{ name: 'Jane Smith', age: 32, email: 'jane@example.com', city: 'London' }
];
const columnDefinitions = [
{ field: "name", filter: true, floatingFilter: true },
{ field: "age", filter: true, floatingFilter: true },
{ field: "email", filter: true, floatingFilter: true },
{ field: "city", filter: true, floatingFilter: true }
];
bru.visualize('table', {
name: 'table1',
provider: 'ag-grid',
props: { rowData, columnDefinitions }
});
This will render a table using the ag-grid provider with filters enabled on all columns.
Using react-table
Example:
const rowData1 = Array.from({ length: 2500 })
.map((_) => [
{ firstName: 'Tanner', lastName: 'Linsley', age: 24, visits: 100 },
{ firstName: 'Tandy', lastName: 'Miller', age: 40, visits: 40 },
{ firstName: 'Joe', lastName: 'Dirte', age: 45, visits: 20 },
]).flat();
const columnDefinitions1 = [
{
id: "firstName",
cell: (info) => info.getValue(),
header: "First Name",
meta: { filterVariant: "text" },
},
{
id: "lastName",
cell: (info) => info.getValue(),
header: "Last Name",
meta: { filterVariant: "text" },
},
{
id: "age",
cell: (info) => info.getValue(),
header: "Age",
meta: { filterVariant: "range" },
},
{
id: "visits",
cell: (info) => info.getValue(),
header: "Visits",
meta: { filterVariant: "range" },
}
];
bru.visualize('table', {
name: 'table2',
provider: 'react-table',
props: { rowData: rowData1, columnDefinitions: columnDefinitions1 }
});
The header property only accepts string values. Use strings like header: "Column Name".
This example renders a large table using the react-table provider, with custom headers and filter variants.
HTML visualization ('html')
You can render either raw HTML (existing behavior) or a Handlebars template with structured data.
Raw HTML (content)
Pass a full HTML string. This mode works in QuickJS safe mode and behaves as before.
Example:
const htmlString = `
<html>
<head>
<style>
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid black; padding: 8px; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<table>
<tr><th>Name</th><th>Age</th><th>Email</th><th>City</th></tr>
<tr><td>John Doe</td><td>28</td><td>john@example.com</td><td>New York</td></tr>
<tr><td>Jane Smith</td><td>32</td><td>jane@example.com</td><td>London</td></tr>
</table>
</body>
</html>
`;
bru.visualize('html', {
name: 'htmlReport',
content: htmlString
});
This example will render an HTML table with predefined data using the html type.
Handlebars template (template, data, options)
Use Handlebars when you want a small template plus JSON data instead of building HTML strings in script:
bru.visualize('html', {
name: 'userCard',
template: '<div><h1>{{title}}</h1><p>{{message}}</p></div>',
data: { title: 'Hello', message: res.body?.name || 'Guest' },
options: {} // optional; mirrors Postman’s third argument when used
});
Bruno compiles template with data on the server and opens the result in the Visualize tab. rawData is passed alongside rendered HTML.
bru.clearVisualizations()
Clears every visualization registered for the current request and resets the internal list (same idea as Postman’s pm.visualizer.clear()).
bru.clearVisualizations();
Postman pm.visualizer mapping
When you import a Postman collection, script calls are translated automatically:
| Postman | Bruno |
|---|
pm.visualizer.set(template, data) | bru.visualize('html', { template, data }) (plus name when you author in Bruno) |
pm.visualizer.set(template, data, options) | bru.visualize('html', { template, data, options }) |
pm.visualizer.clear() | bru.clearVisualizations() |
Export from Bruno back to Postman performs the inverse mapping for the same patterns. Table visualizations (ag-grid, react-table) and raw content HTML are unchanged by this translation.
Import/export translation is covered by the collection converters; see Postman migration and Converters overview.
Using API Response Data
One of the most powerful features of bru.visualize is the ability to transform API responses into visual tables. Here are practical examples of working with real API data:
In Bruno, the parsed API response is stored in res.body
// ✅ Correct
const data = res.body;
// ❌ Incorrect
const data = res.data;
Example 1: Simple User List API
For an API that returns a list of users:
// API URL: https://jsonplaceholder.typicode.com/users
// First, let's check the response structure
console.log('Response:', res);
console.log('Response body:', res.body);
// Bruno stores the parsed response in res.body
const users = res.body;
// Verify we have data
if (!Array.isArray(users)) {
console.error('Expected array but got:', typeof users);
return;
}
console.log('Number of users:', users.length);
const columnDefinitions = [
{ field: "id", filter: true },
{ field: "name", filter: true, floatingFilter: true },
{ field: "email", filter: true, floatingFilter: true },
{ field: "phone", filter: true },
{ field: "website", filter: true }
];
bru.visualize('table', {
name: 'usersTable',
provider: 'ag-grid',
props: {
rowData: users,
columnDefinitions
}
});
Example 2: Nested API Response Data
For APIs returning nested objects, you can flatten the data before visualization:
// API URL: https://jsonplaceholder.typicode.com/users
// Bruno stores the parsed response in res.body
const users = res.body;
// Transform nested data into flat structure
const flattenedData = users.map(user => ({
id: user.id,
name: user.name,
email: user.email,
city: user.address?.city || 'N/A',
company: user.company?.name || 'N/A',
phone: user.phone
}));
const columnDefinitions = [
{ field: "id", filter: true },
{ field: "name", filter: true, floatingFilter: true },
{ field: "email", filter: true, floatingFilter: true },
{ field: "city", filter: true, floatingFilter: true },
{ field: "company", filter: true, floatingFilter: true },
{ field: "phone", filter: true }
];
bru.visualize('table', {
name: 'usersWithDetails',
provider: 'ag-grid',
props: {
rowData: flattenedData,
columnDefinitions
}
});
For complex responses that need transformation before visualization:
// API URL: https://jsonplaceholder.typicode.com/posts
// Bruno stores the parsed response in res.body
const posts = res.body;
// Transform and enhance data
const tableData = posts.slice(0, 20).map((post, index) => ({
index: index + 1,
title: post.title.substring(0, 50) + '...', // Truncate long titles
body: post.body.substring(0, 100) + '...', // Truncate body
userId: post.userId,
postId: post.id,
length: post.body.length
}));
const columnDefinitions = [
{ field: "index", filter: false, width: 80 },
{ field: "postId", filter: true, width: 100 },
{ field: "userId", filter: true, width: 100 },
{ field: "title", filter: true, floatingFilter: true, width: 300 },
{ field: "body", filter: true, width: 400 },
{ field: "length", filter: true, width: 100 }
];
bru.visualize('table', {
name: 'postsTable',
provider: 'ag-grid',
props: {
rowData: tableData,
columnDefinitions
}
});
Example 4: Error Handling with API Data
Always include error handling when working with API responses:
try {
// Bruno stores the parsed response in res.body
// Check if response has data
if (!res.body) {
console.error('No response body found');
return;
}
const data = res.body;
// Check if data is an array (for direct array responses)
// or if it has a data property (for wrapped responses)
const rowData = Array.isArray(data) ? data : data.data;
if (!Array.isArray(rowData)) {
console.error('Invalid response format. Expected array but got:', typeof rowData);
console.log('Response structure:', data);
return;
}
// Verify data is not empty
if (rowData.length === 0) {
console.log('No data to visualize');
return;
}
console.log(`Visualizing ${rowData.length} records`);
const columnDefinitions = [
{ field: "id", filter: true },
{ field: "name", filter: true, floatingFilter: true },
{ field: "email", filter: true, floatingFilter: true }
];
bru.visualize('table', {
name: 'safeDataTable',
provider: 'ag-grid',
props: {
rowData: rowData,
columnDefinitions
}
});
} catch (error) {
console.error('Visualization error:', error.message);
console.error('Stack trace:', error.stack);
}
When working with API responses:
- Always verify the response structure before accessing nested properties
- Use optional chaining (
?.) to safely access nested data
- Transform data to match your visualization needs
- Add meaningful column headers using the
field property
- Enable filters for better data exploration
Using API Response Data with HTML
You can dynamically generate HTML from API responses:
// API URL: https://jsonplaceholder.typicode.com/users
// Bruno stores the parsed response in res.body
const users = res.body;
// Generate table rows from API data
const tableRows = users.slice(0, 10).map(user => `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.phone}</td>
</tr>
`).join('');
const htmlString = `
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
h1 { color: #333; }
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th {
background-color: #4CAF50;
color: white;
padding: 12px;
text-align: left;
}
td {
border: 1px solid #ddd;
padding: 10px;
}
tr:hover { background-color: #f5f5f5; }
.summary {
background-color: #e3f2fd;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1>User Report</h1>
<div class="summary">
<strong>Total Users:</strong> ${users.length}
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
${tableRows}
</tbody>
</table>
</body>
</html>
`;
bru.visualize('html', {
name: 'userReport',
content: htmlString
});
Custom Dashboard with Statistics
Create rich dashboards with API data:
// Example with custom stats or API response
// Bruno stores the parsed response in res.body
const apiData = res.body;
// Calculate or extract statistics from your API response
const stats = {
totalRequests: apiData.length || 0,
successRate: 98.5,
avgResponseTime: 145
};
const htmlString = `
<html>
<head>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 20px;
background-color: #f8f9fa;
}
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-title {
color: #666;
font-size: 14px;
margin-bottom: 10px;
}
.card-value {
font-size: 32px;
font-weight: bold;
color: #333;
}
.card-trend {
color: #4CAF50;
font-size: 14px;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<h1>API Analytics Dashboard</h1>
<p>Last updated: ${new Date().toLocaleString()}</p>
</div>
<div class="cards">
<div class="card">
<div class="card-title">Total Requests</div>
<div class="card-value">${stats.totalRequests || 0}</div>
<div class="card-trend">↑ 12% from last week</div>
</div>
<div class="card">
<div class="card-title">Success Rate</div>
<div class="card-value">${stats.successRate || 0}%</div>
<div class="card-trend">↑ 3% improvement</div>
</div>
<div class="card">
<div class="card-title">Avg Response Time</div>
<div class="card-value">${stats.avgResponseTime || 0}ms</div>
<div class="card-trend">↓ 15ms faster</div>
</div>
</div>
</div>
</body>
</html>
`;
bru.visualize('html', {
name: 'dashboard',
content: htmlString
});
Viewing Your Visualization
- Add the visualization code to your request’s script section
- Execute the request
- Your visualization will be displayed in the panel
Parameters
| Name | Type | Description |
|---|
type | string | Visualization kind: 'table' or 'html'. |
config | object | See Config properties below. Table vs HTML use different fields. |
Config properties
| Property | Type | Used with | Description |
|---|
name | string | table, html | Instance label in the Visualize tab. |
provider | string | table only | ag-grid or react-table. |
props | object | table only | Provider-specific row/column config. |
content | string | html (raw) | Full HTML string (safe mode OK). |
template | string | html (Handlebars) | Handlebars source (not in QuickJS safe mode). |
data | object | html (Handlebars) | Template context / Postman data argument. |
options | object | html (Handlebars, optional) | Extra Handlebars options; mirrors Postman’s third argument when present. |