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.

Prerequisites
  • A Browserless API token from your account dashboard
  • Go with the playwright-go package installed (go get github.com/playwright-community/playwright-go)

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.

FAQ & Troubleshooting

Why am I getting a 403 Forbidden error?

Your API token is missing or expired. Pass it as a ?token= query parameter in the WebSocket or HTTP URL. Verify the token in your account dashboard.

My script works locally but fails on Browserless

Local browser settings may differ from the Browserless environment. Use launch parameters to match your local setup (viewport, user agent, timezone). See launch parameters for the full list.

Next steps