For AI agents: a documentation index is available at /llms.txt
Skip to main content

Running JavaScript with evaluate

The evaluate mutation runs JavaScript in the browser's page environment and returns the result. You can pass either a single expression or a multi-line statement body, and evaluate figures out which form you used.

Prerequisites

Single expressions​

For a single expression, write the expression and evaluate returns its value. You don't need a return statement:

mutation EvaluateExpression {
goto(url: "https://example.com") {
status
}

evaluate(content: "document.title") {
value
}
}

Multi-line statement bodies​

For anything longer than one expression, use triple quotes (""") and write your code as a normal statement body. Use your own return to pass a value back. No IIFE (Immediately Invoked Function Expression) wrapper is required:

mutation MultiLineEvaluate {
goto(url: "https://example.com") {
status
}

evaluate(content: """
const title = document.title;
const url = window.location.href;
return JSON.stringify({ title, url });
""") {
value
}
}

Earlier versions only accepted single expressions, so multi-line code had to be wrapped in an IIFE by hand. That's no longer necessary. Existing IIFE-wrapped scripts still work, so you don't need to rewrite anything that already runs.

Example use cases:

  • Extract complex data from the DOM
  • Manipulate page elements dynamically, such as deleting or adding elements to the HTML
  • Perform calculations or transformations

Advanced Examples​

Extracting Complex Data​

mutation ExtractComplexData {
goto(url: "https://example.com") {
status
}

evaluate(content: """
try {
// Extract all links with their text and href.
const links = Array.from(document.querySelectorAll('a')).map(link => ({
text: link.textContent.trim(),
href: link.href,
isExternal: !link.href.includes(window.location.hostname)
}));

// Get page metadata.
const meta = {
title: document.title,
description: document.querySelector('meta[name="description"]')?.content || '',
keywords: document.querySelector('meta[name="keywords"]')?.content || '',
viewport: document.querySelector('meta[name="viewport"]')?.content || ''
};

return JSON.stringify({ links, meta, error: null });
} catch (e) {
return JSON.stringify({ links: null, meta: null, error: (e?.message ?? String(e)) });
}
""") {
value
}
}

Dynamic Element Manipulation​

mutation ManipulateElements {
goto(url: "https://example.com") {
status
}

evaluate(content: """
try {
// Remove all ads.
const ads = document.querySelectorAll('[class*="ad"], [id*="ad"], [class*="banner"]');
ads.forEach(ad => ad.remove());

// Add a custom element.
const customDiv = document.createElement('div');
customDiv.innerHTML = '<h2>Custom Content Added by BrowserQL</h2>';
customDiv.style.cssText = 'background: #f0f0f0; padding: 20px; margin: 20px 0; border-radius: 5px;';
document.body.insertBefore(customDiv, document.body.firstChild);

return JSON.stringify({
removedAds: ads.length,
addedElement: 'Custom div added to page',
error: null
});
} catch (e) {
return JSON.stringify({ removedAds: null, addedElement: null, error: (e?.message ?? String(e)) });
}
""") {
value
}
}

Data Transformation and Calculations​

mutation DataTransformation {
goto(url: "https://example.com") {
status
}

evaluate(content: """
try {
// Extract and transform price data.
const priceElements = document.querySelectorAll('[class*="price"], [class*="cost"]');
const prices = Array.from(priceElements).map(el => {
const text = el.textContent;
const price = parseFloat(text.replace(/[^0-9.]/g, ''));
return {
originalText: text,
numericValue: price,
currency: text.match(/[$€£¥]/)?.[0] || 'unknown'
};
}).filter(p => !isNaN(p.numericValue));

// Calculate statistics.
const values = prices.map(p => p.numericValue);
const total = values.reduce((sum, value) => sum + value, 0);
const average = values.length > 0 ? total / values.length : 0;
const min = values.length > 0 ? Math.min(...values) : null;
const max = values.length > 0 ? Math.max(...values) : null;

return JSON.stringify({
prices,
statistics: { total, average, min, max, count: prices.length },
error: null
});
} catch (e) {
return JSON.stringify({ prices: null, statistics: null, error: (e?.message ?? String(e)) });
}
""") {
value
}
}

When automating interactions with links that have target="_blank", you can modify them to open in the same browser session. This simplifies your script by avoiding the need to switch tabs or manage multiple page contexts.

mutation avoidNewTab {
goto(url: "https://www.example.com", waitUntil: networkIdle) {
status
}
evaluate(content: """
const link = document.querySelector('a');
if (link && link.target === '_blank') {
link.target = '_self';
}
""") {
time
}
click(selector: "a") {
time
}
}

This approach modifies links from opening in new tabs to opening in the same session, keeping your automation workflow simple and avoiding complex tab management.

Best Practices​

  1. Pick the right form: Use a bare expression when you need a single value, and a multi-line body with your own return for anything more involved.
  2. Return meaningful data: In a statement body, return the data you need. Without a return, value comes back empty.
  3. Handle errors gracefully: Wrap operations in try-catch blocks so a failure returns a useful message instead of breaking the mutation.
  4. Keep it focused: Write focused, single-purpose scripts for better maintainability.

FAQ & Troubleshooting​

Why am I getting a 403 Forbidden response?

Your API token is missing or malformed. Pass it as a ?token= query parameter. Check your account dashboard to verify the token is active.

My query times out before completing

Increase the timeout parameter on your mutation or use a more specific waitUntil condition. Long-running pages may need networkIdle instead of load.

Next steps​

Ready to explore more advanced BrowserQL features? Check out these related topics:

Was this page helpful?