Skip to main content

Language Basics

Learning a new language can be intimidating, but mastering BQL doesn't have to be difficult. This guide provides a straightforward overview of how to write BQL with a clear.

What You Won't Write

BrowserQL is optimized for web automation and scraping, designed to minimize complexity by making intelligent assumptions. Here’s what it does for you:

  • Waits for selectors before interacting with elements.
  • Handles mouse movements and clicks automatically.
  • Ensures elements are visible by scrolling as needed.
  • Manages page-load events, like waiting for firstContentfulPaint.

Instead of worrying about these technical details, you focus on queries and actions.

What You’ll Write

BQL is a query language where you:

  1. Navigate to pages.
  2. Perform actions (e.g., click, type).
  3. Extract data (e.g., text, HTML).

With just a handful of commands (goto, text, html, click, type and more), you can achieve complex automation in a fraction of the usual code.

For example, the script below demonstrates how easy it is to automate a logging into Cloudflare in 30 lines:

mutation Cloudflare {
goto(
url: "https://dash.cloudflare.com/login?lang=en-gb"
waitUntil: firstContentfulPaint
) {
status
}

acceptCookies: click(
selector: "#onetrust-accept-btn-handler"
) {
time
}

typeEmail: type(
selector: "form [data-testid='login-input-email']"
text: "test@browserless.io"
) {
selector
}

typePassword: type(
selector: "form [data-testid='login-input-password']"
text: "super-cool-password"
) {
selector
}

clickCaptcha: verify(
type: cloudflare
) {
solved
}
}

Creating a BQL Script

Below are steps into creating a BQL script, guiding you into navigating to a page, retrieving data, performing actions, and, finally, generating an endpoint to connect to external libraries.

Every script starts with a mutation, specifying actions and the responses you expect:

mutation ExampleName {
goto(
url: "https://example.com"
waitUntil: firstMeaningfulPaint
) {
status
time
}
}

This standard format is composed of the following:

  1. Action: goto specifies the page to navigate to.
  2. Arguments: Provide the url and a waitUntil condition.
  3. Response: Request useful outputs, like status or time.
Mutations

You can find detailed information on all mutations, their arguments, and responses in the Mutations Reference page and also in the Built-in Documentation in our IDE.

Retrieving Data

Extracting information is just as simple. Use text or html commands depending on the format you need:

mutation ExampleName {
...
productName: text(
selector: "span#productTitle"
visible: true
) {
text
}
}

Where:

  • Alias: productName is the name for this interaction. You can define names for each interaction in the script.
  • Action: text extracts visible text.
  • Arguments: Include a selector and optional conditions like visible.
  • Output: The desired response (e.g., text content).

Example JSON response:

"productName": {
"text": "Coffee and Espresso Maker"
}
Pro Tip

Omit the selector to retrieve the entire page’s content.

Performing Actions

Many cases for gathering data or normalizing data across systems require you click, type and submit forms. All of these are staples of any web automation library and are supported by BrowserQL.

By default, BrowserQL takes care of waiting for these elements to be present in the document, visible to an end user, and scrolls to them in the viewport. You won't have to worry about writing code to handle those small nuanced situations.

To click the Free Trial link at the Browserless.io website, you need to navigate to the website with the goto mutation, and then use the click to select the <a> element. In the example below, we'll use the element's href to find the correct element on screen. Also, the visible flag specifies that this element must be visible to the user.

mutation ClickButton {
goto(url: "https://www.browserless.io/" waitUntil: firstContentfulPaint) {
status
}

click(selector: "a[href=\"https://account.browserless.io/signup/email/?plan=starter\"]", visible: true) {
x
y
}
}

For actions such as the need to get past a captcha, BQL takes humanized actions, such as moving the mouse to a selector and randomized typing delays. By default, BrowserQL will type a character at a time with a random time between strokes similar to a real user.

Delay Change

If you wish to change this delay you can specify a min and max delay. Below, typing delays are randomized between 10–50 milliseconds, mimicking natural input:

teapotTyping: type(
text: "I'm a little teapot!"
selector: "form textarea"
delay: [10, 50]
) {
time
}

Handling CAPTCHA

If you know that a page is going to have a captcha, such as for a login or form submission, you can use the verification mutation. This will click on the captcha, even if it’s hidden away in iframe and shadow DOMs. Just specify the CAPTCHA type (e.g., hcaptcha or cloudflare), and BQL takes care of the rest:

verifyCaptcha: verify(type: hcaptcha) {
time
found
solved
}

Using Aliases for Field Naming

In BrowserQL, every mutation can be given an alias to customize how the result appears in your JSON response. While aliases are optional in many cases, they become required when you have naming conflicts—such as multiple mutations of the same type in a single query.

When Aliases Are Required

GraphQL requires unique field names within the same query. If you try to use the same mutation type multiple times without aliases, you'll encounter errors. Common scenarios include:

  • Multiple clicks: Clicking different buttons in sequence
  • Multiple type operations: Filling different form fields
  • Multiple text extractions: Getting content from various page elements
  • Multiple goto operations: Navigating to different pages in one query

Example: Naming Conflicts

❌ This will fail because both click operations have the same field name:

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

click(selector: "#email-field") {
x
y
}

click(selector: "#password-field") { # ❌ Error: Duplicate field name
x
y
}
}

✅ This works with proper aliases:

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

clickEmail: click(selector: "#email-field") {
x
y
}

clickPassword: click(selector: "#password-field") {
x
y
}

typeEmail: type(
selector: "#email-field"
text: "user@example.com"
) {
time
}

typePassword: type(
selector: "#password-field"
text: "securepassword"
) {
time
}

submitLogin: click(selector: "#login-button") {
x
y
}
}

JSON Response with Aliases

The aliases become the keys in your JSON response, making the data structure clear and meaningful:

{
"data": {
"goto": {
"status": 200
},
"clickEmail": {
"x": 150,
"y": 200
},
"clickPassword": {
"x": 150,
"y": 250
},
"typeEmail": {
"time": 245
},
"typePassword": {
"time": 189
},
"submitLogin": {
"x": 200,
"y": 350
}
}
}

Best Practices for Aliases

  1. Use descriptive names: clickSubmit is better than click1
  2. Be consistent: Use a naming pattern like actionTarget (e.g., clickLogin, typeEmail)
  3. Avoid reserved words: Don't use GraphQL keywords as aliases
  4. Keep them concise: Long aliases make queries harder to read
Pro Tip

Even when aliases aren't strictly required, using them makes your queries more readable and your response data easier to work with programmatically.

Connecting Libraries with Endpoints

We also know that you might want to connect other libraries, like Puppeteer or Playwright, to these browsers once they’ve got past the bot detectors. You can create an endpoint with the reconnect action, and use this endpoint to connect to the browser:

reconnect(timeout: 30000) {
browserWSEndpoint
}

Next Steps

BrowserQL simplifies web automation with intuitive commands and a structure that’s easy to learn. Start by focusing on the following: