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

Go (playwright-go)

playwright-go is the community-maintained Go port of Playwright. It supports the same ConnectOverCDP method the other Playwright SDKs use, so connecting to Browserless works the same way. Point it at a wss:// endpoint with your API token as a query parameter. For available connection parameters, see connection URL patterns.

Basic usage

The example below connects to Browserless, navigates to example.com, and prints the page title:

package main

import (
"fmt"
"log"

"github.com/playwright-community/playwright-go"
)

const token = "YOUR_API_TOKEN_HERE" // Replace with your actual token.

func main() {
pw, err := playwright.Run()
if err != nil {
log.Fatalf("could not start playwright: %v", err)
}
defer pw.Stop()

// Connect to Browserless over CDP.
browser, err := pw.Chromium.ConnectOverCDP(
fmt.Sprintf("wss://production-sfo.browserless.io?token=%s", token),
)
if err != nil {
log.Fatalf("could not connect to browserless: %v", err)
}
defer browser.Close()

page, err := browser.NewPage()
if err != nil {
log.Fatalf("could not create page: %v", err)
}

if _, err = page.Goto("https://www.example.com/"); err != nil {
log.Fatalf("could not navigate: %v", err)
}

title, err := page.Title()
if err != nil {
log.Fatalf("could not get title: %v", err)
}
fmt.Printf("The page's title is: %s\n", title)
}

Output

The page's title is: Example Domain

Using an external proxy

When you pass an external proxy URL as a query parameter, any special characters in the credentials (like @, :, or #) need to be percent-encoded or the URL parser will misinterpret them. In Go, use url.QueryEscape for this:

package main

import (
"fmt"
"log"
"net/url"

"github.com/playwright-community/playwright-go"
)

const (
token = "YOUR_API_TOKEN_HERE"
proxyHost = "proxy.example.com"
proxyPort = "8080"
proxyUser = "username"
proxyPass = "p@ssw0rd#123" // Special chars that need encoding.
)

func main() {
pw, err := playwright.Run()
if err != nil {
log.Fatalf("could not start playwright: %v", err)
}
defer pw.Stop()

// Build and encode the proxy URL so special characters don't break the query string.
proxyURL := fmt.Sprintf("http://%s:%s@%s:%s", proxyUser, proxyPass, proxyHost, proxyPort)
encodedProxy := url.QueryEscape(proxyURL)

wsEndpoint := fmt.Sprintf(
"wss://production-sfo.browserless.io?token=%s&externalProxyServer=%s",
token,
encodedProxy,
)

browser, err := pw.Chromium.ConnectOverCDP(wsEndpoint)
if err != nil {
log.Fatalf("could not connect: %v", err)
}
defer browser.Close()

page, err := browser.NewPage()
if err != nil {
log.Fatalf("could not create page: %v", err)
}

if _, err = page.Goto("https://httpbin.org/ip"); err != nil {
log.Fatalf("could not navigate: %v", err)
}

body, err := page.Locator("body").TextContent()
if err != nil {
log.Fatalf("could not read body: %v", err)
}
fmt.Println(body)
}

Output

{
"origin": "proxy-ip-here"
}

For more proxy options (built-in residential/datacenter proxies, context-level proxy config), see Proxies.

Stealth mode

To bypass bot detection, swap the base path to /stealth (or /chromium/stealth). Everything else stays the same:

wsEndpoint := fmt.Sprintf(
"wss://production-sfo.browserless.io/stealth?token=%s",
token,
)

You can combine stealth with any other query parameters:

wsEndpoint := fmt.Sprintf(
"wss://production-sfo.browserless.io/stealth?token=%s&externalProxyServer=%s&timeout=300000",
token,
encodedProxy,
)

See Stealth Routes for the full list of stealth options.

Passing launch parameters

launch parameters and Browserless-specific options go directly in the query string. Because fmt.Sprintf builds a plain string, you can append as many parameters as you need:

wsEndpoint := fmt.Sprintf(
"wss://production-sfo.browserless.io?token=%s&--window-size=1920,1080&blockAds=true&timeout=60000",
token,
)

For the full list of available parameters, see launch parameters.

Go-specific tips

  • Always call url.QueryEscape on values that contain special characters before inserting them into the query string. Go's fmt.Sprintf does no escaping on its own, so unencoded @ or # characters will corrupt the URL.
  • Use defer browser.Close() to make sure the remote browser is released even if your program panics or returns early. Leaked sessions count against your concurrency limit.
  • ConnectOverCDP vs Connect: ConnectOverCDP uses the Chrome DevTools Protocol and is the recommended method for Browserless. Connect uses Playwright's native protocol, which requires a Playwright-specific server and won't work with Browserless endpoints. Stick with ConnectOverCDP.