Skip to content
browserless logo

browserless

Browserless cloud browser automation service. Run headless Chrome at scale for scraping, screenshots, and PDF generation. Use for cloud browser automation, scalable scraping, or headless Chrome as a service.

SKILL.md

Full skill instructions

Browserless Skill

Complete guide for Browserless - headless Chrome as a service.

Quick Reference

Key Features

FeatureDescription
REST APIHTTP endpoints
WebSocketPuppeteer/​Playwright connection
ScreenshotsPage captures
PDFDocument generation
ScrapingData extraction
FunctionsCustom scripts

Endpoints

/​screenshot - Capture screenshots
/​pdf - Generate PDFs
/​content - Get page HTML
/​scrape - Extract data
/​function - Run custom code

1. Setup

Self-Hosted (Docker)

docker run -d \
  -p 3000:3000 \
  -e "TOKEN=your-token" \
  -e "CONCURRENT=10" \
  -e "QUEUED=10" \
  --name browserless \
  ghcr.io/​browserless/​chromium

Docker Compose

services:
  browserless:
    image: ghcr.io/​browserless/​chromium
    ports:
      - "3000:3000"
    environment:
      - TOKEN=your-secure-token
      - CONCURRENT=10
      - QUEUED=50
      - TIMEOUT=60000
      - MAX_PAYLOAD_SIZE=5mb
      - HEALTH_CHECK=true
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 4G

Cloud Service

Sign up at browserless.io
Get API token
Use: wss://chrome.browserless.io?token=YOUR_TOKEN

2. REST API

Screenshots

# Basic screenshot
curl -X POST "http://localhost:3000/screenshot?token=your-token" \
  -H "Content-Type: application/​json" \
  -d '{"url": "https://example.com"}' \
  -o screenshot.png

# With options
curl -X POST "http://localhost:3000/screenshot?token=your-token" \
  -H "Content-Type: application/​json" \
  -d '{
    "url": "https://example.com",
    "options": {
      "fullPage": true,
      "type": "png",
      "quality": 80
    },
    "viewport": {
      "width": 1920,
      "height": 1080
    },
    "waitForSelector": "#content"
  }' \
  -o screenshot.png

PDF Generation

curl -X POST "http://localhost:3000/pdf?token=your-token" \
  -H "Content-Type: application/​json" \
  -d '{
    "url": "https://example.com",
    "options": {
      "format": "A4",
      "printBackground": true,
      "margin": {
        "top": "1cm",
        "right": "1cm",
        "bottom": "1cm",
        "left": "1cm"
      }
    }
  }' \
  -o document.pdf

Get Content

# Get HTML
curl -X POST "http://localhost:3000/content?token=your-token" \
  -H "Content-Type: application/​json" \
  -d '{"url": "https://example.com"}' \
  -o page.html

Scrape Data

curl -X POST "http://localhost:3000/scrape?token=your-token" \
  -H "Content-Type: application/​json" \
  -d '{
    "url": "https://example.com",
    "elements": [
      {"selector": "h1", "name": "title"},
      {"selector": ".price", "name": "price"},
      {"selector": "img", "name": "images", "attr": "src"}
    ]
  }'

3. JavaScript Integration

Using Puppeteer

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.connect({
    browserWSEndpoint: "ws://localhost:3000?token=your-token",
  });

  const page = await browser.newPage();
  await page.goto("https://example.com");

  const title = await page.title();
  console.log("Title:", title);

  await browser.close();
})();

Using Playwright

const { chromium } = require("playwright");

(async () => {
  const browser = await chromium.connectOverCDP(
    "ws://localhost:3000?token=your-token",
  );

  const context = await browser.newContext();
  const page = await context.newPage();

  await page.goto("https://example.com");
  const title = await page.title();
  console.log("Title:", title);

  await browser.close();
})();

REST API with Fetch

async function takeScreenshot(url) {
  const response = await fetch(
    "http://localhost:3000/screenshot?token=your-token",
    {
      method: "POST",
      headers: { "Content-Type": "application/​json" },
      body: JSON.stringify({
        url,
        options: { fullPage: true },
      }),
    },
  );

  const buffer = await response.arrayBuffer();
  return Buffer.from(buffer);
}

async function generatePDF(url) {
  const response = await fetch("http://localhost:3000/pdf?token=your-token", {
    method: "POST",
    headers: { "Content-Type": "application/​json" },
    body: JSON.stringify({
      url,
      options: { format: "A4", printBackground: true },
    }),
  });

  return await response.arrayBuffer();
}

async function scrapeData(url, elements) {
  const response = await fetch(
    "http://localhost:3000/scrape?token=your-token",
    {
      method: "POST",
      headers: { "Content-Type": "application/​json" },
      body: JSON.stringify({ url, elements }),
    },
  );

  return await response.json();
}

4. Python Integration

Using Pyppeteer

import asyncio
from pyppeteer import connect

async def main():
    browser = await connect(
        browserWSEndpoint='ws://localhost:3000?token=your-token'
    )

    page = await browser.newPage()
    await page.goto('https://example.com')

    title = await page.title()
    print(f'Title: {title}')

    await browser.close()

asyncio.run(main())

Using Playwright

from playwright.async_api import async_playwright
import asyncio

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.connect_over_cdp(
            'ws://localhost:3000?token=your-token'
        )

        page = await browser.new_page()
        await page.goto('https://example.com')

        title = await page.title()
        print(f'Title: {title}')

        await browser.close()

asyncio.run(main())

REST API with Requests

import requests

def screenshot(url, options=None):
    response = requests.post(
        'http://localhost:3000/screenshot',
        params={'token': 'your-token'},
        json={
            'url': url,
            'options': options or {'fullPage': True}
        }
    )
    return response.content

def pdf(url, options=None):
    response = requests.post(
        'http://localhost:3000/pdf',
        params={'token': 'your-token'},
        json={
            'url': url,
            'options': options or {
                'format': 'A4',
                'printBackground': True
            }
        }
    )
    return response.content

def scrape(url, elements):
    response = requests.post(
        'http://localhost:3000/scrape',
        params={'token': 'your-token'},
        json={
            'url': url,
            'elements': elements
        }
    )
    return response.json()

# Usage
screenshot_data = screenshot('https://example.com')
with open('screenshot.png', 'wb') as f:
    f.write(screenshot_data)

data = scrape('https://example.com', [
    {'selector': 'h1', 'name': 'title'},
    {'selector': '.price', 'name': 'price'}
])
print(data)

5. Function API

Custom Functions

// POST /​function
{
  "code": `
    export default async ({ page }) => {
      await page.goto('https://example.com');

      // Custom logic
      await page.waitForSelector('.loaded');

      const data = await page.evaluate(() => {
        return {
          title: document.title,
          items: Array.from(document.querySelectorAll('.item'))
            .map(el => ({
              name: el.querySelector('.name').textContent,
              price: el.querySelector('.price').textContent
            }))
        };
      });

      return { data, type: 'application/​json' };
    }
  `
}

With Context

{
  "code": `
    export default async ({ page, context }) => {
      const { url, searchTerm } = context;

      await page.goto(url);
      await page.type('#search', searchTerm);
      await page.click('#submit');
      await page.waitForNavigation();

      const results = await page.$$eval('.result', els =>
        els.map(el => el.textContent)
      );

      return { data: results, type: 'application/​json' };
    }
  `,
  "context": {
    "url": "https://example.com",
    "searchTerm": "puppeteer"
  }
}

6. Advanced Options

Viewport and Device

{
  "url": "https://example.com",
  "viewport": {
    "width": 375,
    "height": 812,
    "deviceScaleFactor": 2,
    "isMobile": true,
    "hasTouch": true
  },
  "userAgent": "Mozilla/​5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)"
}

Wait Conditions

{
  "url": "https://example.com",
  "waitForSelector": "#content",
  "waitForTimeout": 2000,
  "waitForEvent": "networkidle0",
  "waitForFunction": "window.loaded === true"
}

Authentication

{
  "url": "https://example.com",
  "authenticate": {
    "username": "user",
    "password": "pass"
  },
  "cookies": [
    {
      "name": "session",
      "value": "abc123",
      "domain": "example.com"
    }
  ]
}

JavaScript Injection

{
  "url": "https://example.com",
  "addScriptTag": [
    { "content": "window.injected = true;" },
    { "url": "https://cdn.example.com/script.js" }
  ],
  "addStyleTag": [{ "content": "body { background: red; }" }]
}

7. Proxy Support

Using Proxy

{
  "url": "https://example.com",
  "launch": {
    "args": ["--proxy-server=http://proxy.example.com:8080"]
  }
}

Authenticated Proxy

const browser = await puppeteer.connect({
  browserWSEndpoint: "ws://localhost:3000?token=your-token",
});

const page = await browser.newPage();

await page.authenticate({
  username: "proxy-user",
  password: "proxy-pass",
});

await page.goto("https://example.com");

8. Docker Configuration

Full Configuration

services:
  browserless:
    image: ghcr.io/​browserless/​chromium
    ports:
      - "3000:3000"
    environment:
      # Authentication
      - TOKEN=your-secure-token

      # Concurrency
      - CONCURRENT=10
      - QUEUED=50

      # Timeouts
      - TIMEOUT=60000
      - CONNECTION_TIMEOUT=30000

      # Resources
      - MAX_PAYLOAD_SIZE=10mb
      - MAX_CPU_PERCENT=99
      - MAX_MEMORY_PERCENT=99

      # Features
      - ENABLE_CORS=true
      - ENABLE_API_GET=true
      - HEALTH_CHECK=true
      - DEBUG=browserless*

      # Function limits
      - FUNCTION_ENABLE_INCOGNITO_MODE=true
      - FUNCTION_EXTERNALS=["lodash","moment"]

      # Proxy
      - DEFAULT_LAUNCH_ARGS=["--proxy-server=http://proxy:8080"]

    volumes:
      - ./​downloads:/​downloads
      - ./​tmp:/​tmp

    restart: unless-stopped

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/"]
      interval: 30s
      timeout: 10s
      retries: 3

    deploy:
      resources:
        limits:
          cpus: "2"
          memory: 4G

9. Monitoring

Health Check

curl http://localhost:3000/
# Returns: {"status": "ok", ...}

curl http://localhost:3000/stats
# Returns queue and session stats

Metrics Endpoint

curl http://localhost:3000/metrics
# Prometheus-compatible metrics

Debugging

# Enable debug logging
docker run -e "DEBUG=browserless*" ghcr.io/​browserless/​chromium

# View live sessions
curl http://localhost:3000/sessions

10. Common Patterns

Batch Processing

const urls = ["https://example1.com", "https://example2.com"];

async function batchScreenshots(urls) {
  const results = await Promise.all(
    urls.map(async (url) => {
      const response = await fetch(
        "http://localhost:3000/screenshot?token=your-token",
        {
          method: "POST",
          headers: { "Content-Type": "application/​json" },
          body: JSON.stringify({ url, options: { fullPage: true } }),
        },
      );
      return { url, screenshot: await response.arrayBuffer() };
    }),
  );
  return results;
}

Retry Logic

async function withRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

const screenshot = await withRetry(() => takeScreenshot("https://example.com"));

Queue Management

const pLimit = require('p-limit');

const limit = pLimit(5); // Max 5 concurrent

const urls = [...]; // Many URLs

const results = await Promise.all(
  urls.map(url =>
    limit(() => takeScreenshot(url))
  )
);

11. Troubleshooting

Common Issues

Connection timeout:

// Increase connection timeout
const browser = await puppeteer.connect({
  browserWSEndpoint: "ws://localhost:3000?token=your-token",
  timeout: 60000,
});

Memory issues:

# Docker resource limits
deploy:
  resources:
    limits:
      memory: 8G

Queue full:

# Increase queue size
docker run -e "QUEUED=100" ghcr.io/​browserless/​chromium

Session leak:

// Always close browser
try {
  // ... operations
} finally {
  await browser.close();
}

Best Practices

  1. Use tokens - Secure your instance
  2. Set timeouts - Prevent hanging sessions
  3. Limit concurrency - Based on resources
  4. Close sessions - Prevent memory leaks
  5. Use health checks - Monitor availability
  6. Queue management - Handle bursts
  7. Retry logic - Handle transient failures
  8. Resource limits - Docker constraints
  9. Use functions - Complex workflows
  10. Monitor metrics - Track performance