Custom Views
Custom Views display your data in multiple, easy-to-read tables in the Results
page.
For example, if your scraper returns product data with nested reviews, you can create
- An "Overview" table with key details like name and price
- A "Reviews" table that expands each review into its own row
This not only enhances readability but also allows users to export the data in structured format (CSV/JSON) by selecting the view.
Creating a Simple View​
The code below adds a view called "Overview" that shows name
and price
fields.
import { Server } from 'botasaurus-server/server';
import { View, Field } from 'botasaurus-server/ui';
import { yourScraper } from '../src/yourScraper'; // your scraper function
/* 1. Define the view */
const overviewView = new View('Overview', [
new Field('name'),
new Field('price'),
]);
/* 2. Register scraper + view */
Server.addScraper(yourScraper, { views: [overviewView] });
How it works
new View('Overview', …)
creates a tab called Overviewnew Field('name')
/new Field('price')
creates columnsServer.addScraper(…, { views: [overviewView]})
adds theOverview
view to the scraper
Types of Fields​
You can use 4 types of fields to build views.
Field
​
Displays a single value from your data record. It's the most common field.
Options
outputKey
renames the column header, such as changingprice
toproduct_price
map
transforms the value using a function. Perfect for calculating an average, formatting a date, or adding a currency symbolshowIf
conditionally omits the column based on input form data
Example
new Field("reviews_per_rating", {
outputKey: "average_rating",
map: (value, record) => {
// ... Logic to calculate average rating from the 'value' object
},
// Only show this column if the user checked "scrape_prices"
showIf: (inputData) => inputData.scrape_prices === true
})
CustomField
​
Creates a new column from multiple values in the record.
Supports showIf
option only.
Example
// Combines two fields into one
new CustomField("full_name", (record) => `${record.first_name} ${record.last_name}`
)
ExpandDictField
​
Expands a single dictionary object into multiple columns.
Supports showIf
option only.
Example
// Convert a rating like {1: 0, 2: 0, 3: 0, 4: 100, 5: 900} into 5 new columns
new ExpandDictField("reviews_per_rating",[
new Field("1", { outputKey: "rating_1" }),
new Field("2", { outputKey: "rating_2" }),
new Field("3", { outputKey: "rating_3" }),
new Field("4", { outputKey: "rating_4" }),
new Field("5", { outputKey: "rating_5" }),
],
)
ExpandListField
​
Expands an array of objects into multiple rows. Each item in the array becomes a new row.
For example, if a product has 3 reviews, it creates 3 rows in the table.
Supports showIf
option only.
Example
new ExpandListField('reviews', [
new Field('rating'),
new Field('content'),
])
Real World Example​
Let's say a scraper returns the following product data.
const taskScraper = task({
name: "taskScraper",
run: () => {
return [
{
id: 1,
name: "T-Shirt",
price: 16, // in US Dollar
reviews: 1000,
reviews_per_rating: {
1: 0,
2: 0,
3: 0,
4: 100,
5: 900,
},
featured_reviews: [
{
id: 1,
rating: 5,
content: "Awesome t-shirt!",
},
{
id: 2,
rating: 5,
content: "Amazing t-shirt!",
},
],
},
{
id: 2,
name: "Laptop",
price: 700,
reviews: 500,
reviews_per_rating: {
1: 0,
2: 0,
3: 0,
4: 100,
5: 400,
},
featured_reviews: [
{
id: 1,
rating: 5,
content: "Best laptop ever!",
},
{
id: 2,
rating: 5,
content: "Great laptop!",
},
],
},
];
},
})
The following code creates 3 views from this data
- "Overview" shows a summary of each product with expanded rating data
- "Featured Reviews" displays a detailed list where each review is its own row
import { Server } from 'botasaurus-server/server';
import { View, Field, ExpandDictField, ExpandListField, CustomField } from 'botasaurus-server/ui';
import { scrapeProductData } from '../src/scrapeProductData'; // <-- your scraper
/**
* Calculate the average product-rating.
*
* @param value - The `reviews_per_rating` dictionary.
* @param record - The entire product record.
*/
function calculateAverageRating(value: Record<string, number>): number {
const totalReviews = Object.values(value || {}).reduce((a, b) => a + b, 0);
if (totalReviews === 0) return 0;
let ratingSum = 0;
for (const [rating, count] of Object.entries(value)) {
ratingSum += Number(rating) * count;
}
return parseFloat((ratingSum / totalReviews).toFixed(2));
}
const overviewView = new View('Overview', [
new Field('id'),
new Field('name'),
new Field('price'),
new Field('reviews'),
// Create a new column 'average_rating' by transforming 'reviews_per_rating'
new Field('reviews_per_rating', {
outputKey: 'average_rating',
map: calculateAverageRating,
}),
// Expand the 'reviews_per_rating' dictionary into individual rating columns
new ExpandDictField('reviews_per_rating', [
new Field('1', { outputKey: 'rating_1' }),
new Field('2', { outputKey: 'rating_2' }),
new Field('3', { outputKey: 'rating_3' }),
new Field('4', { outputKey: 'rating_4' }),
new Field('5', { outputKey: 'rating_5' }),
]),
]);
const featuredReviewsView = new View('Featured Reviews', [
// Alias parent data for clarity in the reviews table
new Field('id', { outputKey: 'product_id' }),
new Field('name', { outputKey: 'product_name' }),
// Expand the list of featured reviews into separate rows
new ExpandListField('featured_reviews', [
new Field('rating'),
new Field('content'),
]),
]);
Server.addScraper(
scrapeProductData,
{
views: [
overviewView,
featuredReviewsView,
],
}
);
Result
An All Fields
view is always included by default, showing all raw data returned by the scraper without any transformations.