# Automate a cloud browser with Puppeteer
URL: /cookbook/puppeteer

---
title: Automate a cloud browser with Puppeteer
description: Use Steel with Puppeteer in TypeScript for cloud browser automation.
---

<RecipeJsonLd slug="puppeteer" title={"Automate a cloud browser with Puppeteer"} description={"Use Steel with Puppeteer in TypeScript for cloud browser automation."} authors={[{"handle":"hussufo","name":"Hussien Hussien"}, {"handle":"junhsss","name":"Jun Ryu"}]} datePublished="2024-11-19" dateModified="2026-04-24" sourceUrl="https://github.com/steel-dev/steel-cookbook/tree/e86cfbf8ba715cdbbc49fc2ef13e9fd7798695dc/examples/puppeteer-ts" />

<RecipeMeta href="https://github.com/steel-dev/steel-cookbook/tree/e86cfbf8ba715cdbbc49fc2ef13e9fd7798695dc/examples/puppeteer-ts" path="examples/puppeteer-ts" authors={[{"handle":"hussufo","name":"Hussien Hussien","avatar":"https://github.com/hussufo.png?size=40"}, {"handle":"junhsss","name":"Jun Ryu","avatar":"https://github.com/junhsss.png?size=40"}]} updated="2026-04-24" />

<RecipeQuickstart slug="puppeteer-ts" />

Puppeteer ships a `connect()` call that attaches to any Chrome exposing a DevTools websocket. Steel sessions expose one. Point Puppeteer at it and the rest of the script is plain Puppeteer: `page.goto`, `page.evaluate`, `page.waitForSelector`, the whole surface area. Stealth, proxies, and the live session viewer come from Steel without extra wiring.

```typescript
session = await client.sessions.create();

browser = await puppeteer.connect({
  browserWSEndpoint: `${session.websocketUrl}&apiKey=${STEEL_API_KEY}`,
});

const page = await browser.newPage();
```

Two notes on the shape here. First, the package is `puppeteer-core`, not `puppeteer`. There's no Chromium to download because the browser lives on Steel. Second, `browser.newPage()` opens a fresh tab in the Steel session; the session viewer starts blank until you navigate.

## Run it

```bash
cd examples/puppeteer-ts
cp .env.example .env          # set STEEL_API_KEY
npm install
npm start
```

Get a key at [app.steel.dev/settings/api-keys](https://app.steel.dev/settings/api-keys). The script prints a session viewer URL as it starts. Open it in another tab to watch the browser run live.

Your output varies. Structure looks like this:

```text
Creating Steel session...
Steel Session created!
View session at https://app.steel.dev/sessions/ab12cd34…

Connected to browser via Puppeteer
Navigating to Hacker News...

Top 5 Hacker News Stories:

1. Claude 4.7 Opus released today
   Link: https://news.ycombinator.com/item?id=43218921
   Points: 892

2. Show HN: A browser extension for reading on slow connections
   Link: https://github.com/user/project
   Points: 401

…

Releasing session...
Session released
Done!
```

A run costs a few cents of browser time. Steel bills per session-minute, so the `finally` block that calls `client.sessions.release()` isn't optional. Forgetting it keeps the browser running until the default 5-minute timeout.

## Make it yours

- **Swap the target.** Replace the `page.goto` URL and the `page.evaluate` body in `main()`. Session setup, connect, and cleanup stay the same.
- **Turn on stealth.** Uncomment `useProxy`, `solveCaptcha`, or `sessionTimeout` in the `sessions.create()` call for sites with anti-bot.
- **Persist login.** Reuse cookies and local storage across runs via [credentials](/cookbook/credentials).

## Related

[Puppeteer docs](https://pptr.dev)

## Related recipes

<RecipeGrid>
<RecipeCard slug="trigger-dev-browser-job" title={"Run a Steel browser job with Trigger.dev"} description={"Queue a Trigger.dev task that creates a Steel session, drives Playwright over CDP, saves artifacts, and releases the browser in cleanup."} topics={['Browser automation', 'Trigger.dev']} languages={['TypeScript']} date="2026-06-29" />
<RecipeCard slug="temporal-browser-workflow" title={"Run a durable browser workflow with Temporal"} description={"Build a Temporal TypeScript Workflow that schedules retryable Steel browser Activities to capture page summaries, screenshots, and Markdown artifacts."} topics={['Browser automation', 'Temporal']} languages={['TypeScript', 'Python', 'Rust', 'Go']} date="2026-06-29" />
<RecipeCard slug="headless-chrome" title={"Automate a cloud browser with headless_chrome"} description={"Use Steel with headless_chrome, the synchronous Rust equivalent of Puppeteer, to connect over CDP and scrape quotes with element handles."} topics={['Browser automation']} languages={['Rust']} date="2026-06-24" />
</RecipeGrid>
