Skip to main content

Best BQL Practices

We recommend you use the following best practices to make the most out of BQL. This guide covers essential patterns that will make your browser automation more reliable and maintainable.

Prefer Variables Over Hardcoded Values

Use GraphQL variables to keep your BQL operations reusable and easy to maintain. Variables let you parameterize values like URLs, selectors, and input data, so you can avoid hardcoding and repeating them across queries.

# Avoiding concatenating strings by using GraphQL variables
mutation LoginUser($email: String!, $password: String!, $loginUrl: String!) {
goto(url: $loginUrl, waitUntil: firstContentfulPaint) {
status
}

typeEmail: type(
selector: "input[type='email']"
text: $email
) {
time
}

typePassword: type(
selector: "input[type='password']"
text: $password
) {
time
}

submitLogin: click(selector: "button[type='submit']") {
x
y
}
}

Then, send a variables object in your JSON payload (read more about them here)[https://graphql.org/learn/queries/#variables]:

{
"email": "user@example.com",
"password": "securepassword123",
"loginUrl": "https://example.com/login"
}

Prefer Built-in Waiters Over Arbitrary Sleeps

Use BQL’s built-in waiters to make your automations more reliable. Smart waiters like waitUntil and waitForSelector respond to real page conditions, avoiding the fragility of fixed delays with waitForTimeout.

mutation SearchProducts($url: String!, $searchTerm: String!) {
# Navigate and wait until the network is idle
goto(url: $url, waitUntil: networkIdle) {
url
}
type(text: $searchTerm, selector: "input[name='search']") {
selector
}
click(selector: "button[type='submit']") {
selector
}

# Wait until the result items are present and visible
waitForSelector(selector: ".product-list .product-item", visible: true) {
selector
}
html(visible: true) {
html
}
}

Prefer Semantic Selectors Over Flaky CSS Classes

Prefer semantic selectors for more reliable automations. Use stable HTML attributes, roles, or other meaningful selectors that are less likely to change, and avoid relying on CSS classes that can break with UI updates.

mutation AddProductToCart($url: String!) {
goto(url: $url, waitUntil: networkIdle) {
status
}
# Use HTML attributes for more reliable scraping
click(selector: "[data-testid='add-to-cart-button']") {
selector
}
# Use HTML roles instead of classes for waiting for a particular selector
waitForSelector(
selector: "[role='status'][aria-label='cart-item-count']"
visible: true
) {
selector
}
html(visible: true) {
html
}
}

Block Unnecessary Resources

Block unnecessary resources to improve performance. Use the reject mutation to prevent loading ads, images, videos, and other non-essential requests, reducing bandwidth usage and speeding up execution.

# Make sure to enable the Adblock parameter in the editor!
mutation Search($url: String!, $query: String!) {
# Reject images, styles and media right away
reject(
type: [image, stylesheet, media]
) {
time
}
goto(url: $url, waitUntil: networkIdle) {
status
}
type(text: $query, selector: "input[name='q']") {
selector
}
click(selector: "button[type='submit']") {
selector
}
waitForNavigation {
status
}
html(visible: true) {
html
}
}

Use Fragments to Avoid Repeating Query Parts

GraphQL fragments allow you to reuse common field selections and reduce duplication in your queries.

fragment NavigationResponse on HTTPResponse {
status
time
url
}

mutation ReusableFragments {
homePage: goto(url: "https://example.com", waitUntil: firstContentfulPaint) {
...NavigationResponse
}

loginClick: click(selector: "a[href='/login']") {
selector
}

loginPage: goto(url: "https://example.com/login", waitUntil: firstContentfulPaint) {
...NavigationResponse
}

submitClick: click(selector: "button[type='submit']") {
selector
}
}

Use the proxySticky Parameter

When using GraphQL clients like Apollo or Relay, ensure caching is turned off so browser automation steps always run fresh and return up-to-date results.

https://production-sfo.browserless.io/chromium/bql?token=YOUR_API_TOKEN_HERE&proxy=residential&proxyCountry=us&proxySticky=true

Disable Mutation Caching in GraphQL Clients

When using GraphQL clients like Apollo or Relay, disable caching for BQL mutations since browser automation results are not cacheable and should always execute fresh.

import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({
uri: 'https://production-sfo.browserless.io/chromium/bql?token=YOUR_TOKEN',
cache: new InMemoryCache()
});

const BQL_MUTATION = gql`
mutation ScrapePage($url: String!) {
goto(url: $url, waitUntil: firstContentfulPaint) {
status
}
content: text {
text
}
}
`;

const result = await client.mutate({
mutation: BQL_MUTATION,
variables: { url: 'https://example.com' },
fetchPolicy: 'no-cache'
});

Summary

The tl; dr is:

  • Use variables to make queries reusable and easy to maintain.
  • Rely on built-in waiters for intelligent waits based on real page conditions.
  • Choose semantic selectors over CSS classes for greater stability.
  • Block unnecessary resources to improve performance and reduce costs.
  • Use fragments to avoid duplication and keep queries clean.
  • Configure sticky proxies for consistent IP addresses and improved stealth.
  • Disable client-side caching so browser automation always runs fresh.

For more advanced techniques, explore the BQL Recipes section and refer to the API Reference for complete mutation documentation.