# Render JavaScript before you read the page

> Load React, Vue and other single-page apps in a headless browser and get the rendered page as Markdown, HTML or text. $0.005 per page, errors free.

Some pages are empty until their scripts run. Add js_render=true and Grabber runs the page in a headless browser first, then returns what it shows in any format.

- $1 free credit, no card
- No subscription
- Failed requests are free

[Get API key](https://dashboard.grabber.sh/signup) · [Read the docs](https://grabber.sh/docs/parameters)

## One parameter, the rendered page

Add js_render=true to any request. Everything else stays the same.

- Works with every format
- Up to 120 seconds per page
- $0.005 per page, only when you ask

### cURL

```bash
curl -G https://api.grabber.sh/fetch \
  -H "Authorization: Bearer $GRABBER_API_KEY" \
  --data-urlencode "url=https://app.example.com/pricing" \
  -d "formats=markdown" \
  -d "js_render=true" \
  -d "unwrap=true"
```

### Python

```python
import os
import requests

response = requests.get(
    "https://api.grabber.sh/fetch",
    params={"url": "https://app.example.com/pricing", "formats": "markdown", "js_render": "true", "unwrap": "true"},
    headers={"Authorization": f"Bearer {os.environ['GRABBER_API_KEY']}"},
    timeout=130,
)
response.raise_for_status()
print(response.text)
```

### Node.js

```javascript
const params = new URLSearchParams({ url: 'https://app.example.com/pricing', formats: 'markdown', js_render: 'true', unwrap: 'true' })

const response = await fetch(`https://api.grabber.sh/fetch?${params}`, {
  headers: { Authorization: `Bearer ${process.env.GRABBER_API_KEY}` },
  signal: AbortSignal.timeout(130_000),
})
if (!response.ok) throw new Error(`Grabber answered ${response.status}`)
console.log(await response.text())
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"time"
)

func main() {
	query := url.Values{"url": {"https://app.example.com/pricing"}, "formats": {"markdown"}, "js_render": {"true"}, "unwrap": {"true"}}
	req, _ := http.NewRequest("GET", "https://api.grabber.sh/fetch?"+query.Encode(), nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("GRABBER_API_KEY"))

	client := &http.Client{Timeout: 130 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status)
	fmt.Println(string(body))
}
```

### PHP

```php
<?php
$query = http_build_query(['url' => 'https://app.example.com/pricing', 'formats' => 'markdown', 'js_render' => 'true', 'unwrap' => 'true']);

$ch = curl_init("https://api.grabber.sh/fetch?$query");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('GRABBER_API_KEY')],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 130,
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body;
```

### Response body

```markdown
# Pricing

| Plan | Price | Seats |
| --- | --- | --- |
| Starter | $19 a month | 3 |
| Team | $49 a month | 10 |

All plans include a 14-day trial.
```

## How it works

1. **Add js_render=true.** Keep the rest of your request as it is: the URL, the formats and unwrap.
2. **Grabber runs the page.** The page loads in a headless browser and its scripts run, so the content they build is there.
3. **You get what it shows.** The rendered page comes back in the formats you asked for. Allow up to 120 seconds.

## What people build with it

### Single-page apps

Read sites built with React, Vue, Angular or Svelte that send an empty page and fill it in the browser.

### Pricing and plan pages

Many pricing tables are loaded by scripts. Rendering gets you the numbers, not a spinner.

### Content loaded later

Reviews, listings and comments that arrive after the first load are in the rendered page.

### Agents on modern sites

Let your agent retry with js_render when a page comes back empty.

## Pricing

Prepaid credit, taken one request at a time. Ask for several formats in one request at the price of one.

| Request | Price |
| --- | --- |
| A page | **$0.001** |
| A page with `js_render=true` | **$0.005** |
| Any error | $0 |
| Credit when you sign up | $1, about 1,000 pages |

## Questions

### When do I need JavaScript rendering?

When a page comes back empty or says to enable JavaScript. Try without it first: a plain fetch is faster and costs $0.001 instead of $0.005.

### How long does a rendered page take?

Usually a few seconds, and never more than 120. Set your client timeout to at least 130 seconds.

### Can it sign in or solve CAPTCHAs?

No. Grabber reads public pages as a signed-out visitor, with or without rendering.

### Am I charged if the render fails?

No. Any error, including a render that times out, costs nothing.

## Related

- [URL to Markdown](https://grabber.sh/url-to-markdown): Clean Markdown for LLMs and RAG.
- [Link extractor](https://grabber.sh/link-extractor): Every link on a page, as absolute URLs.
- [HTML to text](https://grabber.sh/html-to-text): Plain text with no markup at all.
- [MCP server](https://grabber.sh/mcp): The same API as a tool for your agent.

## Give your agents the web

Sign up in a minute and fetch your first 1,000 pages for free.

[Get API key](https://dashboard.grabber.sh/signup) · [Read the docs](https://grabber.sh/docs)
