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.QueryEscapeon values that contain special characters before inserting them into the query string. Go'sfmt.Sprintfdoes 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. ConnectOverCDPvsConnect:ConnectOverCDPuses the Chrome DevTools Protocol and is the recommended method for Browserless.Connectuses Playwright's native protocol, which requires a Playwright-specific server and won't work with Browserless endpoints. Stick withConnectOverCDP.
Related docs
- Connection URL patterns — endpoint construction and query parameters
launchparameters — browser flags and Browserless-specific settings- Proxies — built-in and external proxy configuration
- Stealth Routes — bot detection bypass
- Session management — reconnection and persistent sessions
- Playwright customizations — advanced Playwright configuration