Browser Recipe blocks
Every block you can use in a POST /crawl recipe, with its fields, defaults and failure behaviour.
The recipe
Send the recipe as the JSON body of POST https://app.irmu.com/api/crawl. Premium pool, country and city go in the query string.
{
"name": "My recipe",
"expectedOutput": "item",
"blocks": [
{
"id": "b1",
"type": "start",
"details": {
"type": "url",
"source": "https://example.com/"
}
},
{
"id": "b2",
"type": "extract",
"details": {
"name": "title",
"selector": "h1",
"property": "text"
}
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
| name | string | – | Optional label for your own logs. |
| expectedOutput | "item" | "list" | "item" | How the result is returned. See How the result is built. |
| blocks* | array | – | Run in order, top to bottom. The first block must be a start block. |
Every block has the same shape:
| Field | Type | Default | Description |
|---|---|---|---|
| id* | string | – | Any string unique within the recipe. paginate refers to blocks by it. |
| type* | string | – | One of the block types below. |
| details* | object | – | The block's own fields. Use {} for blocks that take none. |
How the result is built
- The crawl fills a list of rows. extract writes match n into row n and creates rows as needed.
- Every other data block writes into row 0.
- With expectedOutput "list", the rows come back as an array.
- With "item", row 0 comes back as one object; a field present in later rows becomes an array, and url is the start block's source.
Pitfalls
- Don't send a top-level id on the recipe: the API rejects it with 422. Only name, expectedOutput and blocks are accepted.
- click and input fail the crawl when nothing matches; scroll-to-element, remove and extract skip instead.
- add-field and extract-variable need row 0 to exist; on an empty result the crawl fails.
- paginate only stops when a pass adds no rows, so include an extract inside the loop.
Navigation and page control
start
Opens the page and gets past bot challenges. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| type* | "url" | – | "url" is the only supported source. |
| source* | string | – | The URL to open. |
- • Always the first block. Cloudflare, DDoS-Guard, EdgeOne, DataDome and PerimeterX challenges and captchas are handled automatically.
- • A 404 ends the crawl with no result.
- • A recipe with only a start block returns the raw HTML of the page instead of JSON.
- • Only source is read from the start block. Page-load options (wait_until, js, adblock, premium, country, city) go in the POST /crawl query string.
{
"id": "b1",
"type": "start",
"details": {
"type": "url",
"source": "https://example.com/"
}
}wait
Pauses for a number of seconds. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| seconds* | number | – | How long to pause. Fractions are allowed (0.5). |
{
"id": "w1",
"type": "wait",
"details": {
"seconds": 2
}
}click
Clicks an element. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| selector* | string | – | CSS selector. The first match is clicked. |
- • If no element matches, the crawl fails. When the element might be missing, put a remove or wait before it.
{
"id": "c1",
"type": "click",
"details": {
"selector": "button.load-more"
}
}input
Types text into an element. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| selector* | string | – | CSS selector of the field to type into. |
| text* | string | – | Text to type. It is added to whatever the field already holds. |
- • If no element matches, the crawl fails.
{
"id": "i1",
"type": "input",
"details": {
"selector": "input[name=q]",
"text": "running shoes"
}
}scroll-to-element
Scrolls an element into view. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| selector* | string | – | CSS selector. The first match is scrolled into view. |
- • If nothing matches, the block is skipped. Useful for triggering lazy loading, for example by scrolling to the footer.
{
"id": "s1",
"type": "scroll-to-element",
"details": {
"selector": "footer"
}
}remove
Removes elements from the page. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| selector* | string | – | Every element it matches is removed. |
| timeout | number (ms) | 10000 | How long to wait for the selector. If it never appears, the block is skipped. |
- • Use it to get rid of cookie banners and overlays before a click or screenshot.
{
"id": "r1",
"type": "remove",
"details": {
"selector": "#cookie-banner"
}
}paginate
Goes to the next page and runs earlier blocks again. Writes: –
| Field | Type | Default | Description |
|---|---|---|---|
| startBlock* | string | – | The id of the block to jump back to. It must exist and can't be the first block. |
| selector | string | – | The "next page" button. It is clicked, then the crawl waits 5 seconds. |
- • Pagination stops when a full pass adds no new rows, so the loop must include an extract block.
- • Use expectedOutput: "list".
{
"id": "next",
"type": "paginate",
"details": {
"startBlock": "extract-title",
"selector": "a.next"
}
}Extracting data
extract
Reads a value from every element a selector matches. Writes: Row by row
| Field | Type | Default | Description |
|---|---|---|---|
| name* | string | – | The field to write. |
| selector* | string | – | CSS selector, looked up inside parentSelector if given, otherwise the whole page. |
| property* | string | – | text, html, an attribute (href, src, aria-label…) or dataset.<key>. Several can be comma-separated; the first with a value wins. |
| parentSelector | string | – | Defines the rows. A row that lacks the field stays empty and later rows keep their values. |
| timeout | number (ms) | 10000 | How long to wait for the selector (or parentSelector). If it never appears, the block is skipped. |
- • A field named price longer than 20 characters is trimmed to the first currency amount found.
{
"id": "e1",
"type": "extract",
"details": {
"name": "url",
"parentSelector": ".card",
"selector": "a.title",
"property": "href"
}
}jsonschema
Reads a schema.org object from JSON-LD or microdata. Writes: Row 0 (replaces it)
| Field | Type | Default | Description |
|---|---|---|---|
| type* | string | – | The schema.org @type to find, such as Product, Hotel or Recipe. Not case-sensitive. |
| timeout | number (ms) | 30000 | How long to wait for JSON-LD before falling back to microdata. |
- • Searches nested objects. If the JSON-LD object is sparse, missing fields are filled from microdata.
{
"id": "j1",
"type": "jsonschema",
"details": {
"type": "Product"
}
}datalayer
Reads the product from the GTM / GA4 dataLayer. Writes: Row 0
| Field | Type | Default | Description |
|---|---|---|---|
| type* | "ecommerce" | – | ecommerce is the only supported value. |
- • Takes the first product from ecommerce.detail.products (Universal Analytics) or ecommerce.items (GA4), plus currency.
- • For GA4 only single-item events count and view_item_list is skipped, so listings aren't mistaken for a product.
{
"id": "d1",
"type": "datalayer",
"details": {
"type": "ecommerce"
}
}extract-variable
Reads a JavaScript global. Writes: Row 0
| Field | Type | Default | Description |
|---|---|---|---|
| variable* | string | – | Dotted path from window, e.g. __NEXT_DATA__.props.pageProps. |
| field* | string | – | The field to write. |
- • Needs row 0 to exist already: place it after extract or jsonschema.
{
"id": "v1",
"type": "extract-variable",
"details": {
"variable": "__NEXT_DATA__.props.pageProps",
"field": "page"
}
}add-field
Adds a fixed value. Writes: Row 0
| Field | Type | Default | Description |
|---|---|---|---|
| field* | string | – | The field to write. |
| value* | any | – | The value, written as given. |
- • Needs row 0 to exist already: place it after extract or jsonschema.
{
"id": "a1",
"type": "add-field",
"details": {
"field": "source",
"value": "competitor-a"
}
}Capturing the page
screenshot
Takes a full-page JPEG screenshot. Writes: data.screenshots
- • Takes no fields: "details": {}.
- • Make it the last block of an "item" recipe: every screenshot the recipe took is returned as base64 in data.screenshots.
{
"id": "shot",
"type": "screenshot",
"details": {}
}Specialised blocks
ai
Has your language model extract data or act on the page. Writes: Row 0
| Field | Type | Default | Description |
|---|---|---|---|
| query* | string | – | Plain-English instruction: data ("get the product details") or an action ("click on subscribe"). |
- • Uses the language model and key saved in your organization settings (https://irmu.com/docs/dashboard). No key goes in the recipe.
- • Expect POST /crawl to return a job id to poll with GET /crawl/{job} instead of the result.
{
"id": "ai1",
"type": "ai",
"details": {
"query": "Get the product name, price and availability"
}
}chatgpt
Asks ChatGPT and captures the answer. Writes: Row 0 (conversation)
| Field | Type | Default | Description |
|---|---|---|---|
| prompt* | string | – | The question to ask. |
- • Put it after a start block that opens https://chatgpt.com/ with JavaScript on. The answer is returned as structured nodes under conversation.
{
"id": "g1",
"type": "chatgpt",
"details": {
"prompt": "What are the best web scraping APIs?"
}
}booking
Reads a Booking.com hotel page. Writes: Row 0
- • Takes no fields: "details": {}. Returns name, address, rating, price, rooms, facilities and images.
- • Search result pages need extract blocks, or use GET /booking/search.
{
"id": "bk",
"type": "booking",
"details": {}
}google-ai-overview
Reads Google's AI Overview. Writes: Row 0
| Field | Type | Default | Description |
|---|---|---|---|
| field | string | "ai_overview" | Field for the answer text. |
| sourcesField | string | "ai_overview_sources" | Field for the cited sources, as [{ title, url }]. |
| timeout | number (ms) | 15000 | How long to wait for the overview. |
- • Accepts the consent dialog first if one appears. A page without an overview is left as it is.
- • Or call GET /google-ai-overview directly.
{
"id": "aio",
"type": "google-ai-overview",
"details": {}
}A paginated list recipe
{
"name": "Product listing",
"expectedOutput": "list",
"blocks": [
{
"id": "open",
"type": "start",
"details": {
"type": "url",
"source": "https://example.com/shop"
}
},
{
"id": "cookies",
"type": "remove",
"details": {
"selector": "#cookie-banner"
}
},
{
"id": "extract-title",
"type": "extract",
"details": {
"name": "title",
"parentSelector": ".card",
"selector": "h3",
"property": "text"
}
},
{
"id": "extract-price",
"type": "extract",
"details": {
"name": "price",
"parentSelector": ".card",
"selector": ".price",
"property": "text"
}
},
{
"id": "next",
"type": "paginate",
"details": {
"startBlock": "extract-title",
"selector": "a.next"
}
}
]
}Start building with Irmu today
200 free credits every month, no card required. Every API, every integration, one key.