AI Frontier Post
Tutorials

Lightpanda hands-on: the headless browser built for AI agents, from first fetch to a replayable scrape

Headless Chrome is a full desktop browser with the window switched off. Lightpanda is the opposite: a browser written from scratch in Zig for agents and automation, a fraction of the weight, and this guide takes it from first fetch to a replayable scrape — every command verified.

TL;DR: Lightpanda (lightpanda-io/browser, AGPL-3.0) is a headless browser written from scratch in Zig for AI agents and automation: 37,923 stars, on GitHub's daily trending page October 30, 2026. Verified here: nightly 1.1.0-nightly.11326+948d77378 — fetch to Markdown, structured-data dumps, a no-LLM REPL with schema extract, PandaScript replay, CDP + Puppeteer, MCP stdio, and the Python package — all without an account or API key.
  • Who: Lightpanda (lightpanda-io/browser, AGPL-3.0), a headless browser written from scratch in Zig for agents and automation.
  • What: fetch dumps, a no-LLM agent REPL with schema extract, PandaScript replay, a CDP server, an MCP server, and a Python package.
  • When: verified October 30, 2026: nightly 1.1.0-nightly.11326+948d77378, puppeteer-core 25.16.0, lightpanda Python 1.0.2.
  • Requirement: Linux or macOS (WSL on Windows), about 80 MB of disk, a glibc-based system for the Linux binary. No account, no API key — except Step 8's agent mode, which needs a provider key.
A small glowing panda-shaped browser window speeding past a huge rusty chrome locomotive
Illustration generated by AI Frontier Post.

Why this matters now

Every agent framework that browses the web eventually hits the same wall: the browser it drives is a full desktop browser with the window switched off. Headless Chrome carries a rendering engine, a compositor, GPU processes, and hundreds of megabytes of memory into a job that only needs a DOM, some JavaScript, and the text. That is why Lightpanda is climbing GitHub's trending list right now: it is a headless browser written from scratch in Zig, built for machines rather than humans, and it skips everything automation does not need. On October 30, 2026 it sat on GitHub's daily trending page with 37,923 stars, 1,849 forks, and 11,353 commits behind it, under the AGPL-3.0 licence. The project's own benchmark — 933 real pages fetched on an AWS EC2 m5.large instance — reports 123 MB peak memory and 5 seconds for 100 pages, against roughly 2 GB and 46 seconds for headless Chrome. Those are the project's figures, not independent measurements, but the direction is the point: this is a browser that treats memory and startup time as features.

What makes it more than a faster fetcher is that the same binary covers the whole automation ladder. One command dumps a page to Markdown. A built-in agent REPL lets you drive pages with slash commands and no model at all. Sessions save as PandaScript — plain JavaScript files you replay token-free. And when you do want your existing tooling, it speaks the Chrome DevTools Protocol, so Puppeteer and Playwright scripts connect to it with a single-line change. This guide works through that ladder in order, and every command and output below was run in a sandbox on October 30, 2026, against the nightly build. Nothing here requires an account or an API key until the final, clearly marked agent step.

Abstract illustration of a messy web page flowing through glowing stages into neat structured data blocks
Illustration generated by AI Frontier Post.

What you'll need

  • Linux, macOS, or Windows with WSL2. There is no native Windows binary; the Linux build runs inside WSL2, and WSL forwards localhost ports to the Windows host automatically.
  • About 80 MB of disk for the binary. The nightly Linux build downloaded here is 77 MB. No build step, no toolchain, no browser download.
  • A glibc-based system for the Linux binary. The release binaries are linked against glibc, so they fail on musl-based distributions such as Alpine with a missing-loader error. Use a Debian- or Ubuntu-based image, or the official Docker image, instead.
  • No account and no API key for Steps 1–7. Step 8, the LLM agent mode, is the only part that needs a provider key, and it is marked as such.
  • One privacy note before you start. Lightpanda collects usage telemetry by default. Every command in this guide sets LIGHTPANDA_DISABLE_TELEMETRY=true; add it to your shell profile if you want it off permanently.

Step 1 — Install the binary and verify it

The project publishes nightly builds for Linux and macOS on x86_64 and aarch64, plus Homebrew, Arch, Nix, and Docker routes. The most direct path on Linux is the nightly binary:

curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux
chmod a+x ./lightpanda
LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda version

Expected output, with the nightly build current on October 30, 2026:

1.1.0-nightly.11326+948d77378

Nightly builds move fast, so pin the version you verified: if a later nightly changes a flag, your notes should say which build they were written against, and this guide's build is the one above. Prefer a stable route if you want fewer surprises: brew install lightpanda-io/browser/lightpanda on macOS, nix run github:lightpanda-io/browser -- --help with Nix, or docker run -d --name lightpanda -p 127.0.0.1:9222:9222 lightpanda/browser:latest for the official image, which already exposes the CDP server on port 9222. Run ./lightpanda --help at any point to see the eight commands: agent, completion, fetch, help, mcp, run, serve, and version.

Step 2 — Fetch a JavaScript-rendered page as Markdown

fetch is Lightpanda's answer to curl with JavaScript support: it loads the page in a real browser engine, runs its scripts, and dumps the result. The project's own demo site is the right first target, because it is rendered client-side — a plain HTTP fetch would return an empty shell:

LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda fetch --dump markdown https://demo-browser.lightpanda.io/amiibo/

The output is the rendered page as Markdown, and it includes content that only exists after JavaScript runs:

# Sandy

![Amiibo Character Image](https://raw.githubusercontent.com/8bitDream/AmiiboAPI/master/images/icon_04380001-03000502.png)
Game Animal Crossing
Serie Animal Crossing

## See also
- [Isabelle](https://demo-browser.lightpanda.io/amiibo/00440502.html)
- [Coach](https://demo-browser.lightpanda.io/amiibo/00c10502.html)
- [Boo \- Soccer](https://demo-browser.lightpanda.io/amiibo/02a00e02.html)

That distinction matters for agent work: Markdown is the cheapest faithful representation of a page, and it is what you want to feed a model. The --dump flag takes seven modes — html, markdown, pdf, png, semantic_tree, semantic_tree_text, and structured_data. The last one is the most underused: it prints the page's JSON-LD, OpenGraph, Twitter card, meta, and link tags as JSON, which is exactly the metadata a scraper usually re-parses by hand. On example.com it returns:

{"jsonLd":[],"openGraph":{},"twitterCard":{},"meta":{"language":"en","charset":"utf-8","viewport":"width=device-width,initial-scale=1","title":"Example Domain"},"links":{"icon":"data:,"}}

Two more flags are worth knowing now. --dump-selector "<css>" dumps only the first element matching a selector, and fails if nothing matches — useful when a page's navigation dwarfs its content. --json wraps the fetch status and the dump in a machine-readable object, and --fail-on-http-error makes the process exit with code 22 on HTTP 400 and above, which is what you want in a script that should fail loudly rather than scrape an error page.

Step 3 — Make slow pages finish before you read them

The default wait is 5,000 ms after the load event, which is enough for most pages and not enough for some. Lightpanda gives you four ways to wait, and they compose: --wait-until takes load, domcontentloaded, networkalmostidle, networkidle, or done (full quiescence — pages with constant background activity never reach it and run to the timeout); --wait-ms supersedes the others with a flat millisecond budget; --wait-selector waits for an element to appear; and --wait-script waits for a JavaScript expression to turn truthy. Waiting for network idle is the first thing to try on a page that populates its content from API calls, and it is what the amiibo demo was fetched with above:

LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda fetch --dump markdown \
  --wait-until networkidle https://demo-browser.lightpanda.io/amiibo/

Honest limitation, found during verification: on the campfire-commerce demo, the page shell rendered but the product cards stayed on "Loading..." in this sandbox, with the default wait and with an explicit 4,000 ms wait alike, while the amiibo demo rendered completely. The wait flags are the documented remedy for exactly that shape of page — wait for network idle, or for the content's own selector with --wait-selector — but if a page's data endpoint is unreachable from your network, no wait flag fixes it. Treat a persistent "Loading..." in your dump as a signal to inspect the page, not to wait longer. The same section of the manual offers --strip-mode for trimming dumps: clutter for reader-mode extraction, shell for page chrome, ui for images and media, and css, js, and invisible for the obvious tag groups.

Step 4 — Drive it from the REPL without a model

lightpanda agent --no-llm starts a slash-command REPL that drives the same browser without calling any model. It is the fastest way to prototype a scrape: you navigate, read, and extract interactively, and the session records every action. It also accepts piped input, which is how the session below was run — every line is a command, and the last line quits:

printf '/goto https://en.wikipedia.org/wiki/Web_scraping\n/extract schema={"title":"h1","intro":"#mw-content-text p"}\n/save wiki.js\n/quit\n' | LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda agent --no-llm

The output is the working result of the whole tutorial in miniature. Navigation confirms itself, extraction returns structured JSON, and saving writes the session to a file:

● Navigated. HTTP 200 OK.
{
  "title": "Web scraping",
  "intro": "Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites.[1] Web scraping software may directly access the World Wide Web using the Hypertext Transfer Protocol or a web browser. While web scraping can be done manually by a software user, the term typically refers to automated processes implemented using a bot or web crawler. It is a form of copying in which specific data is gathered and copied from the web, typically into a central local database or spreadsheet, for later retrieval or analysis."
}
Saved 3 line(s) to wiki.js

Three details in that transcript are worth learning now, because each one cost a failed attempt during verification. First, /extract takes its schema as schema={...} — a JSON object mapping output field names to CSS-selector specs, not a JSON Schema document, and bare JSON or key=value pairs are rejected with "unknown field". Second, /help takes its topic without a slash (/help extract, not /help /extract). Third, the saved script is only three lines, because with --no-llm the save dumps the recorded actions verbatim rather than synthesizing an idiomatic script — that synthesis is what the LLM mode adds. The REPL's other commands cover the rest of the job: /tree for a semantic overview of the page, /markdown and /html for reading, /links, /click, /fill, /press, /scroll, /screenshot, and /structuredData. Run /help inside the REPL for the full list.

Step 5 — Turn the session into a script you can replay for free

The saved wiki.js reads like this:

const page = new Page();
await page.goto("https://en.wikipedia.org/wiki/Web_scraping");
page.extract({ title: "h1", intro: "#mw-content-text p" });

That is PandaScript: vanilla JavaScript plus a small set of native browser primitives, with Page as the entry point. Replay it with run, which needs no model and no key:

LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda run wiki.js

One behaviour to know: a replayed script prints nothing on its own. The saved script extracts but never logs, so run wiki.js exits 0 in silence. If you want output, write the script by hand and print the result yourself — this is the script this guide recommends keeping, and it was run to produce the output below:

const page = new Page();
await page.goto("https://en.wikipedia.org/wiki/Web_scraping");
const data = await page.extract({ title: "h1", intro: "#mw-content-text p" });
console.log(JSON.stringify(data, null, 2));
LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda run scrape.js
{
  "title": "Web scraping",
  "intro": "Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites.[1] ..."
}

This is the workflow the agent mode is designed around: prototype interactively or with a model, save the session, then ship the script to production, where it runs deterministically with no model at runtime. Two cautions travel with it. Scripts can contain evaluate(...) calls that run arbitrary JavaScript in the page, so treat a PandaScript file the way you treat a shell script and only run ones you trust. And the extract schema has one strict rule: if every top-level key misses, the call errors instead of returning empty values — inspect the page with /tree or /markdown and fix your selectors rather than guessing.

Abstract illustration of a small browser core connected by glowing lines to robot agent figures and code windows
Illustration generated by AI Frontier Post.

Step 6 — Plug it into Puppeteer, Playwright, or MCP

If you already automate browsers, Lightpanda's pitch is that you change one line: replace launching Chrome with connecting to Lightpanda. Start the CDP server:

LIGHTPANDA_DISABLE_TELEMETRY=true ./lightpanda serve --host 127.0.0.1 --port 9222 --log-level error

Check it answers before you wire anything to it. GET http://127.0.0.1:9222/json/version returned, verbatim:

{"Browser": "Lightpanda/1.0", "Protocol-Version": "1.3", "User-Agent": "Lightpanda/1.0", "Lightpanda-Version": "1.1.0-nightly.11326+948d77378", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/"}

Then connect with Puppeteer — the only change from a normal script is connect instead of launch, with the WebSocket endpoint from that version document. With puppeteer-core 25.16.0 on Node 24, this script ran against the server above and printed the first five links from the amiibo demo:

import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({ browserWSEndpoint: 'ws://127.0.0.1:9222' });
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.goto('https://demo-browser.lightpanda.io/amiibo/', { waitUntil: 'networkidle0' });
const links = await page.evaluate(() => Array.from(document.querySelectorAll('a')).map(a => a.getAttribute('href')).slice(0, 5));
console.log(JSON.stringify(links, null, 2));
await page.close();
await context.close();
await browser.disconnect();
[
  "00440502.html",
  "00c10502.html",
  "02a00e02.html",
  "02bd0e02.html",
  "02ee0502.html"
]

Playwright connects the same way over CDP, and a WebDriver BiDi server is available with --protocol webdriver if your harness speaks that protocol instead. The second integration path needs no browser client at all: lightpanda mcp is a native MCP server over stdio, so any MCP-capable agent host can drive the browser as tools. The configuration is one entry:

{
  "mcpServers": {
    "lightpanda": {
      "command": "/path/to/lightpanda",
      "args": ["mcp"]
    }
  }
}

Piping an MCP initialize request at the server in the sandbox returned serverInfo {"name": "lightpanda", "version": "0.1.0"}, and the tool guidance it ships with states its own cost order plainly: start with the semantic tree, scope with nodeDetails or findElement, and read markdown for one subtree before ever dumping a full page. For serving several agents from one process, lightpanda mcp --port 9223 switches the same server to HTTP, with one browsing session per connection, so agents stop clobbering each other's pages.

Step 7 — The same browser from Python

If your pipeline is Python, the lightpanda package on PyPI (version 1.0.2 here) bundles the browser binary, so there is no second install step:

python3 -m venv .venv && source .venv/bin/activate
pip install lightpanda
from lightpanda import Browser
with Browser() as b:
    page = b.new_session()
    page.goto(url="https://en.wikipedia.org/wiki/Web_scraping")
    data = page.extract(schema={"title": "h1", "intro": "#mw-content-text p"})
    print(data["title"])
    print(data["intro"][:120])
Web scraping
Web scraping, web harvesting, or web data extraction is data scraping used for extracting data from websites.[1] Web scr

The method names are generated from the MCP tool schemas, so they track the binary's tools — waitForSelector becomes wait_for_selector, and so on — and an async AsyncBrowser variant covers concurrent sessions. One genuine gotcha from verification: the package's own example scrapes example.com for its h1, but the example.com served in this sandbox carries no h1 element at all — only a <title> — so that example errors with "no schema selector matched any element" here. That is the page changing, not the package breaking, and it is a useful rule in general: when extract fails on every key, check the page before blaming the tool. The Wikipedia example above is the one that ran, so use a page you have inspected when you pick your own selectors.

Step 8 — Agent mode, the one step that needs a key

Everything so far runs without a model. lightpanda agent adds one: describe what you want in plain English, and the agent drives the browser, then /save exports the session as a PandaScript file you replay with run. It supports Anthropic, OpenAI, Gemini, Vertex AI, Mistral, Hugging Face, the Vercel AI Gateway and OpenRouter, any OpenAI-compatible endpoint through OPENAI_BASE_URL, and local models through Ollama, llama.cpp, or LLMD. The entry points, from the command's own help, are:

./lightpanda agent --task "top story on news.ycombinator.com?"
./lightpanda agent --provider gemini --task "..."
./lightpanda agent --task "..." --save out.js
./lightpanda agent --list-models

This step was not run in the sandbox, because it needs a provider key and this guide's rule is to run what it teaches or say so. What was verified is the shape around it: the --no-llm REPL in Step 4 is the same interface with the model removed, the --save output format is the PandaScript of Step 5, and --list-models prints the usable model IDs for the detected provider. If you use agent mode, keep the telemetry note from Step 1 in mind, and remember that the saved script is the artifact worth keeping — it is what turns a one-off agent session into a repeatable job.

When to use this vs alternatives

ToolReach for it whenWatch out for
LightpandaYou run browsers at scale for agents or scraping and pay for every megabyte and second.Nightly builds move fast; pin your version. Web API coverage is still growing.
Headless Chrome + Puppeteer/PlaywrightYou need pixel-perfect rendering or the fullest Web API surface.A full desktop browser per job: gigabytes of memory at scale.
curl / requests / httpxThe page is server-rendered and you only need the raw HTML.Blind to anything JavaScript renders after load.
Scraping frameworks (e.g. Crawl4AI)You want crawl orchestration, chunking, and LLM-ready Markdown out of the box.They still run a browser underneath for JS pages — Lightpanda can be that browser.

The honest summary is that Lightpanda is not a Chrome replacement for everything. It has no visual rendering step — its PNG and PDF dumps are text-only renderings, not screenshots of a rendered page — and its Web API surface is a work in progress, so a page that leans on an unimplemented API will behave differently here than in Chrome. What it is, today, is the cheapest faithful way to give a machine a page: a real DOM, real JavaScript, and a Markdown or JSON dump at a fraction of Chrome's weight. Respect the sites you scrape while you are at it: pass --obey-robots when you want robots.txt honoured, keep request rates reasonable, and check a site's terms before building a pipeline on it.

The takeaway

The browser is the most expensive part of most agent stacks, and it is the part nobody chose deliberately — it is just what the framework launched. Lightpanda reframes that choice: a browser written for automation, in Zig, with no rendering engine, no UI, and no Chrome to keep fed. In one sitting you can install a 77 MB binary, dump a JavaScript-rendered page to Markdown, pull structured data out of it with a CSS schema, prototype the whole job in a REPL that needs no model, and save it as a script that replays for free. The CDP and MCP doors mean your existing Puppeteer scripts and agent hosts do not need rewriting to use it, and the Python package means neither do your pipelines. Start with fetch on a page you own, graduate to a PandaScript when the job repeats, and only reach for the LLM agent mode when the navigation itself is the hard part. That order — dump, extract, script, agent — is the whole tutorial, and it is also the order that keeps your token bill at zero until the job actually needs a mind.

Sources: the Lightpanda README and repository page (install routes, benchmarks, dump modes, wait flags, agent providers, MCP transport, read October 30, 2026), the lightpanda 1.0.2 PyPI page (Python API, method generation), and the verification runs quoted in Steps 1–7 (nightly version, fetch outputs, CDP version document, Puppeteer links, MCP initialize response, REPL transcript, PandaScript replay, Python extract). The 16x/9x figures are the project's benchmark claims, not independent measurements; the campfire-commerce loading behaviour and the example.com h1 absence are sandbox observations reported as such; Step 8's agent commands come from the command's own help text and were not run without a key.