How to Generate Screenshots of Indian Ecommerce Category Pages in PHP with Browsershot
Capture a rendered ecommerce category page in PHP with Browsershot. Choose viewport, full-page, or product-grid output and wait for dynamic content.
Use Spatie Browsershot to render an ecommerce category URL in headless Chrome and save the result as an image. In PHP, the core call is Browsershot::url($url)->save($path). Choose a viewport screenshot for the initial visible area, fullPage() for the whole listing, or select() to capture the product-grid element. For category pages that render products after JavaScript runs, wait for a page-specific selector that you have confirmed on the page you are authorized to capture.
This guide is site-neutral: Indian retailers can change markup, access controls, and consent flows, and no particular merchant page or selector is validated here. Substitute a category URL you are permitted to access, and treat selectors and timing as page-specific.
1. Install Browsershot and prepare the runtime
Browsershot is a PHP interface to Puppeteer, which controls headless Chrome. The rendering process therefore needs PHP dependencies plus Node.js and a compatible Chrome or Chromium installation available to the PHP process. Follow the installation instructions for the Browsershot version in your project; package and browser setup can vary by version and deployment environment.
- Install the package with Composer:
composer require spatie/browsershot. - Install and configure the Node.js and browser runtime required by your installed Browsershot version, following the official requirements.
- Confirm the PHP worker, queue worker, or web process can locate Node and Chrome/Chromium and has permission to execute them and write the output directory.
- Use an application-controlled output path. Create the directory if needed and avoid deriving a filesystem path directly from untrusted URL input.
For Laravel, use storage_path() for application storage. The Laravel Screenshot package is a separate integration with a facade and configurable drivers; its documented Browsershot driver also requires Node.js and Chrome/Chromium. Do not assume that package is installed just because Browsershot is.
2. Capture a category page in PHP
This minimal example saves a viewport screenshot. Replace the example URL with a category URL you are authorized to capture and ensure the destination directory exists and is writable.
<?php
require __DIR__ . '/vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
$url = 'https://example.com/category';
$output = __DIR__ . '/category.png';
Browsershot::url($url)
->windowSize(1440, 1000)
->save($output);
if (! is_file($output) || filesize($output) === 0) {
throw new RuntimeException('Screenshot was not written.');
}
echo "Saved screenshot to {$output}\n";
The fixed viewport makes the captured browser area predictable. The screenshot is only as representative as the target page’s rendering state, viewport, and access response; saving a file alone does not prove that the intended product cards appeared.
3. Choose viewport, full-page, or product-grid capture
| Goal | Browsershot option | Use it when |
|---|---|---|
| Visible browser area | windowSize(width, height) (default screenshot scope) |
You need the category page as a visitor initially sees it. |
| Entire document | fullPage() |
You need the complete listing and the page height is manageable. |
| Product grid only | select(selector) |
A stable selector identifies the listing container and excludes unrelated page regions. |
| Specific rectangle | clip(x, y, width, height) |
The region’s coordinates are known and stable for the selected viewport. |
| Mobile rendering | mobile() with an appropriate viewport |
You need mobile layout behavior that respects the page’s meta viewport. |
The API details and image options are documented in Browsershot’s image documentation. Selectors such as .product-grid below are examples only; inspect the page you are permitted to capture and choose a selector that exists in its rendered DOM.
Full-page listing
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(1440, 1000)
->fullPage()
->save(__DIR__ . '/category-full.png');
Full-page output can be extremely tall for a long catalog. It may consume more browser memory and produce a large image; consider capturing a specific grid or a bounded viewport when one very tall artifact is not useful. A lazy-loaded listing may also need scrolling or an explicit readiness step before capture. Browsershot provides waiting controls, but the correct condition depends on the target page.
Only the product grid
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(1440, 1000)
->waitForSelector('.product-grid')
->select('.product-grid')
->save(__DIR__ . '/category-grid.png');
Replace .product-grid with a selector confirmed on the rendered page. If several elements match, Browsershot’s select supports choosing an element index. A selector that exists before product data arrives may not mean the cards are ready; wait for a more specific card or a page condition that reflects completed rendering.
Clip a fixed region
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(1440, 1200)
->clip(180, 250, 1080, 700)
->save(__DIR__ . '/category-clip.png');
Clipping uses coordinates, so changes in viewport, banners, font loading, or page layout can shift the target. Prefer element selection when the grid has a suitable stable container.
4. Wait for JavaScript-rendered products
Category pages may populate product cards after the initial HTML arrives. Use a readiness condition tied to the rendered page rather than assuming a universal delay. Browsershot documents waits for a selector, a delay, a JavaScript function, and lazy-loaded resources in its image usage options.
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(1440, 1000)
->waitForSelector('.product-card')
->fullPage()
->save(__DIR__ . '/category-ready.png');
The selector above is illustrative. If the listing uses infinite scroll, cards may be added only as the visitor scrolls. A full-page screenshot does not guarantee every deferred product is loaded. Determine the capture requirement first, then use the page’s normal supported behavior to load that content and verify the result. Use a fixed delay only when no stronger condition is available, and calibrate it for your own environment rather than copying an arbitrary number.
5. Set mobile layout and image density
For a mobile-oriented capture, configure a mobile viewport and use Browsershot’s mobile() option, which accounts for the page’s meta viewport. Device scale affects output density and dimensions; higher density can improve sharpness while increasing image size and resource use.
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(390, 844)
->mobile()
->deviceScaleFactor(2)
->save(__DIR__ . '/category-mobile.png');
Check the installed Browsershot version’s documentation for the exact options available. For repeatable comparisons, keep viewport, mobile emulation, device scale, capture scope, and readiness condition consistent between runs.
6. Output format and file handling
PNG is a practical default for crisp product tiles and text. JPEG can reduce file size when lossy compression is acceptable. Browsershot supports image output options documented by the package; use a file extension that matches the chosen output and verify that the resulting file is nonempty and decodes in your downstream workflow. Optional image manipulation features may require the separate spatie/image dependency, as noted in the official docs.
For Laravel, the same basic call can write under storage:
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/category')
->windowSize(1440, 1000)
->save(storage_path('app/category.png'));
7. Optional Laravel Screenshot integration
If your application already uses Spatie’s Laravel Screenshot package, its documented facade form is Screenshot::url($url)->save($path). Driver selection and the available Browsershot customization hook are configured through that package; this is an alternative integration layer, not a requirement for using Browsershot directly.
<?php
use Spatie\LaravelScreenshot\Facades\Screenshot;
Screenshot::url('https://example.com/category')
->save(storage_path('app/category.png'));
The integration’s customization hook exposes the underlying Browsershot instance for settings such as headers, user agent, timeout, and dialog dismissal. Follow the Laravel Screenshot documentation for the package version and driver setup you use. Its docs also describe a Cloudflare Browser Rendering driver; that hosted driver belongs to Laravel Screenshot’s integration choices, not to Browsershot itself.
8. HTTP equivalents for a screenshot service
Browsershot is PHP code that runs a local browser. If you are comparing implementation approaches, hosted screenshot APIs instead accept an HTTP request and return an image or another artifact. These examples show the request pattern for ScreenshotNeo; see the API documentation for parameters and response behavior.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/category \
-o category.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/category"},
timeout=90,
)
r.raise_for_status()
with open("category.webp", "wb") as output:
output.write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/category'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('category.webp', bytes));
Keep API keys on the server and out of browser-delivered code. Check HTTP status and response headers before treating a response as a successful screenshot; consult the docs for the supported parameters and response semantics.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call request returns an image or PDF; check the docs for options such as full-page capture, CSS element capture, viewport and device settings, wait conditions, custom CSS and JavaScript, and caching.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/category -o shot.webp
Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
10. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Node or browser executable not found | The runtime is missing or the PHP process has a different PATH or user environment. | Check Node and Chrome/Chromium paths from the same worker or service account that runs PHP. Configure paths using the options supported by your installed Browsershot version. |
| Browser launch fails | Missing system dependencies, incompatible browser/runtime setup, or insufficient execution permissions. | Follow the version-specific installation requirements, inspect the captured process error, and confirm the service account can launch the browser. |
| Screenshot file is absent or empty | Destination directory is missing or unwritable, or rendering failed before save. | Ensure the directory exists and is writable; log the exception and verify the resulting file size and type. |
| Image shows no product cards | Products render later, the selector does not match, or the page returned a different state. | Confirm the rendered DOM and page response. Wait for a card-specific selector or another observed readiness condition; do not assume a guessed class is valid. |
| Grid is cut off | Viewport capture was used when a full listing or element was needed. | Choose fullPage() or select(); use clipping only when coordinates are stable. |
| Full-page image omits lower products | Lazy loading or infinite scroll has not populated all items at capture time. | Determine how the page loads more items and apply a suitable documented wait or supported scroll/readiness sequence, then inspect the output. |
| Screenshot differs between runs | Viewport, device scale, page data, fonts, consent state, or timing changed. | Record the viewport, scale, scope, URL, and readiness rule. Compare captures under the same conditions. |
| Request is denied or displays a challenge | The target site may restrict automated access or require an interaction. | Capture only pages you are authorized to access and respect site terms and access controls. Do not attempt to defeat a CAPTCHA or access restriction. |
11. Performance, reliability, and cost
- Browser resource use: Each capture launches or uses browser work and consumes CPU and memory. Very tall full-page captures and high device scale increase the amount of image data to render and save.
- Concurrency: Limit simultaneous browser jobs according to the memory and CPU available to the PHP workers. Queue captures for larger batches and set operational timeouts appropriate to the page and environment.
- Repeatability: Fix the viewport, device scale, output scope, and readiness rule. Category contents change over time, so identical code does not guarantee identical pixels.
- Failure handling: Catch and log rendering exceptions, verify output files, and distinguish a browser failure from a valid page that contains no products. Avoid endlessly retrying pages that consistently deny access.
- Cost: Browsershot is open-source PHP software, but self-hosting requires infrastructure for PHP, Node.js, Chrome/Chromium, storage, and the resources used by captures. Hosted APIs instead have their own usage plans and billing rules; check current vendor terms. ScreenshotNeo lists a free tier of 1,000 shots monthly and paid plans starting at $5 for 3,000.
FAQ
Can Browsershot capture a page that requires JavaScript?
Yes. Browsershot controls Puppeteer and headless Chrome, so it captures the browser-rendered page. You still need an appropriate wait condition for content that appears after initial load.
How do I take a full-page screenshot with Browsershot?
Call fullPage() before save(). For deferred listings, establish that the needed products have loaded before capture.
Can I capture only one product card?
Use select() with a selector for the card, or its documented selector index when multiple matches exist. Confirm the selector against the rendered page.
Does this guarantee capture access to a named Indian retailer?
No. Access rules, markup, consent dialogs, and page behavior vary, and this guide does not validate any named retailer. Use an authorized URL and verify the output for your own page.
Is Laravel required?
No. Browsershot can be called directly from PHP. Laravel Screenshot is a separate optional integration.


