Screenshotting a Shopify store looks easier than most sites: URLs are predictable, pages are public, the structure is the same across every store. Then you see gray placeholders instead of product photos, a GDPR banner covering half the screen, and a password wall on the dev store. A Shopify screenshot API integration built on ScreenshotRun handles all of these. This page shows how, with code for automating captures through Shopify webhooks.
Capture a Shopify product page screenshot
Every Shopify store follows the same URL pattern: /products/{handle} for product pages, /collections/{handle} for collections. Pass the full URL to the API and you get back the rendered page as an image.
curl "https://api.screenshotrun.com/v1/screenshots/capture?\
url=https://your-store.myshopify.com/products/classic-leather-jacket&\
format=webp&\
width=1280&\
full_page=true&\
block_cookies=true&\
delay=2000" \
-H "Authorization: Bearer YOUR_API_KEY" \
--output product-page.webp
Two parameters matter more here than on most sites. full_page scrolls the entire product page, picks up reviews, related products, upsell sections, trust badges below the fold, and stitches everything into one tall image. The delay of 2000ms gives Shopify's JavaScript time to swap in lazy-loaded images. Without it, product galleries often render as gray placeholders because the Intersection Observer hasn't triggered yet.
The block_cookies parameter removes GDPR consent banners. Shopify stores targeting EU customers almost always have one, and it will sit on top of your product hero if you don't dismiss it.
Shopify-specific rendering problems
Shopify themes introduce a few challenges that don't come up on static sites. If your screenshots look wrong, one of these is probably why.
Most Shopify themes (Dawn included) use loading="lazy" on product images and Intersection Observer for gallery thumbnails. A standard screenshot fires before images below the viewport have loaded. The fix: use full_page=true with delay=2000. The API scrolls the page first, which triggers all lazy-load observers, then waits for the images to finish loading.
What about stores that show different prices by country? Shopify Markets adjusts currency and amounts depending on visitor location. A store might display $49.99 to US visitors and €44.99 to German ones. To capture the German version, add geolocation=de to your request. The API routes the capture through a German IP, and Shopify's Markets feature responds accordingly.
Password-protected development stores are another common obstacle. Shopify dev stores require a storefront password before showing any content. You can bypass this by passing the password session cookie in the cookies parameter. Submit the password form manually to get the storefront_digest cookie value, then include it in API calls:
curl "https://api.screenshotrun.com/v1/screenshots/capture?\
url=https://dev-store.myshopify.com/products/test-product&\
format=png&\
cookies=storefront_digest%3Dabc123def456" \
-H "Authorization: Bearer YOUR_API_KEY" \
--output dev-preview.png
One caveat: the storefront_digest cookie expires when the store owner changes the password. If your automated captures suddenly start returning the password page, regenerate the cookie.
For capturing competitor storefronts, some merchants install third-party bot protection apps (Sentri, GeoFraudShield) that block datacenter IPs. Add stealth=true to make the headless browser look like a regular desktop visitor. Shopify itself doesn't run Cloudflare in front of storefronts, so the main obstacle is these third-party apps, not Shopify's infrastructure.
Automate Shopify screenshots with webhooks
Shopify fires webhooks on store events: product created, product updated, collection changed, theme published. You can wire these up to capture screenshots automatically whenever something changes. The workflow is straightforward. Shopify sends a webhook to your server, your server calls the ScreenshotRun API with the product URL, the screenshot gets stored wherever you need it.
This Node.js webhook handler captures a Shopify product page screenshot every time a product gets updated. It adds response_type=json so the API returns a hosted screenshot URL instead of raw image bytes:
const express = require("express");
const crypto = require("crypto");
const axios = require("axios");
const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));
app.post("/webhooks/product-update", async (req, res) => {
// Verify Shopify HMAC signature
const hmac = crypto
.createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET)
.update(req.rawBody)
.digest("base64");
if (hmac !== req.headers["x-shopify-hmac-sha256"]) {
return res.status(401).send("Invalid signature");
}
const product = req.body;
const storeUrl = req.headers["x-shopify-shop-domain"];
const productUrl = `https://${storeUrl}/products/${product.handle}`;
const widths = [
{ width: 1280, label: "desktop" },
{ width: 390, label: "mobile" },
];
for (const d of widths) {
try {
const response = await axios.get(
"https://api.screenshotrun.com/v1/screenshots/capture",
{
params: {
url: productUrl,
format: "webp",
width: d.width,
full_page: true,
block_cookies: true,
delay: 2000,
response_type: "json",
},
headers: { Authorization: `Bearer ${process.env.SCREENSHOTRUN_KEY}` },
}
);
console.log(`${d.label}: ${response.data.screenshot_url}`);
} catch (err) {
console.error(`${d.label} failed: ${err.message}`);
}
}
res.status(200).send("OK");
});
app.listen(3000);
Register the webhook in your Shopify Admin under Settings → Notifications → Webhooks, or through the Admin API. Set the event to products/update and point it at your server's endpoint. The HMAC verification at the top of the handler makes sure only Shopify can trigger captures.
Other webhook events worth connecting:
themes/publish— capture key pages after a new theme goes live (visual regression check)products/create— screenshot new product pages for your catalog archivecollections/update— capture collection pages after merchandising changes
Batch screenshot a Shopify store via the Admin API
Need to capture every product in your store? For a catalog archive, a migration audit, a wholesale PDF, or just a visual inventory before a major theme change, pull the product list from Shopify's GraphQL Admin API and feed each URL to the screenshot API.
const axios = require("axios");
const SHOPIFY_STORE = "your-store.myshopify.com";
const SHOPIFY_TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
const SR_KEY = process.env.SCREENSHOTRUN_KEY;
async function getAllProductHandles() {
let handles = [];
let cursor = null;
while (true) {
const query = `{
products(first: 50${cursor ? `, after: "${cursor}"` : ""}) {
edges {
node { handle }
cursor
}
pageInfo { hasNextPage }
}
}`;
const res = await axios.post(
`https://${SHOPIFY_STORE}/admin/api/2025-01/graphql.json`,
{ query },
{ headers: { "X-Shopify-Access-Token": SHOPIFY_TOKEN } }
);
const edges = res.data.data.products.edges;
handles.push(...edges.map((e) => e.node.handle));
if (!res.data.data.products.pageInfo.hasNextPage) break;
cursor = edges[edges.length - 1].cursor;
}
return handles;
}
async function captureAll() {
const handles = await getAllProductHandles();
console.log(`Found ${handles.length} products`);
for (const handle of handles) {
try {
const response = await axios.get(
"https://api.screenshotrun.com/v1/screenshots/capture",
{
params: {
url: `https://${SHOPIFY_STORE}/products/${handle}`,
format: "webp",
width: 1280,
full_page: true,
block_cookies: true,
delay: 2000,
response_type: "json",
},
headers: { Authorization: `Bearer ${SR_KEY}` },
}
);
console.log(`OK: ${handle} → ${response.data.screenshot_url}`);
} catch (err) {
console.error(`FAIL: ${handle} → ${err.message}`);
}
await new Promise((r) => setTimeout(r, 500));
}
}
captureAll();
The script uses cursor-based pagination to pull all product handles from Shopify's GraphQL API, then captures each product page with a 500ms gap between requests. Failed captures get logged but don't stop the batch. For a 200-product store, the whole run takes about 12 minutes. Keep in mind that batch captures of this size consume your monthly quota quickly on the free plan (200 captures/month), so larger stores should be on a paid tier.
Visual QA after Shopify theme deployments
Theme updates on Shopify are the most common source of visual bugs. A CSS change breaks the cart page on mobile. A section reorder shifts the hero image. A new app conflicts with the checkout layout, but nobody notices until a customer emails a screenshot. Agencies who manage dozens of stores know this pain well. The standard QA process? Opening every critical page in the browser and comparing by eye. That works for one store. For twenty, it doesn't.
Wire the themes/publish webhook to capture a set of critical pages automatically. Homepage, a product page, a collection, the cart, contact. Capture each across desktop and mobile. Store the screenshots with the theme version in the filename, and you have a visual record for every deployment. Compare against the previous set to spot regressions before customers report them.
const CRITICAL_PAGES = [
"/",
"/products/your-best-seller",
"/collections/all",
"/cart",
"/pages/contact",
];
const VIEWPORTS = [
{ width: 1280, label: "desktop" },
{ width: 390, label: "mobile" },
];
async function captureQASet(storeUrl, themeVersion) {
for (const page of CRITICAL_PAGES) {
for (const vp of VIEWPORTS) {
const res = await axios.get(
"https://api.screenshotrun.com/v1/screenshots/capture",
{
params: {
url: `https://${storeUrl}${page}`,
width: vp.width,
format: "png",
full_page: true,
block_cookies: true,
block_ads: true,
delay: 2000,
response_type: "json",
},
headers: { Authorization: `Bearer ${process.env.SCREENSHOTRUN_KEY}` },
}
);
const filename = `${themeVersion}_${page.replace(/\//g, "_")}_${vp.label}`;
console.log(`${filename}: ${res.data.screenshot_url}`);
await new Promise((r) => setTimeout(r, 300));
}
}
}
Ten captures (5 pages × 2 viewports) finish in about 40 seconds. For agencies managing 30 client stores, a scheduled run after each theme publish replaces hours of manual checking.
Screenshot competitor Shopify stores
Shopify makes competitor analysis easy in one respect: store URLs are predictable, product pages are publicly accessible, the /collections/all endpoint lists every product. But the "spy tools" in the Shopify app store (Koala Inspector, MarketBull, TrendTrack) extract structured data only. Prices, product counts, collection names. They don't show you what the competitor's store actually looks like. The homepage banner for a flash sale. The new collection layout. The holiday gift guide design. Do you really know what your competitors are doing if you've never seen their storefront?
A cron job that screenshots competitor homepages and key collection pages daily gives you a visual archive that no data scraper captures. Add geolocation to see geo-targeted pricing, full_page for the complete layout, and stealth=true for stores running bot detection apps. This is closer to brand monitoring than traditional competitive intelligence.
import os
import requests
import datetime
SR_KEY = os.environ["SCREENSHOTRUN_KEY"]
competitors = [
{"name": "competitor-a", "url": "https://competitor-a.com"},
{"name": "competitor-b", "url": "https://competitor-b.myshopify.com"},
]
pages = ["/", "/collections/all", "/collections/new-arrivals"]
for shop in competitors:
for page in pages:
response = requests.get(
"https://api.screenshotrun.com/v1/screenshots/capture",
params={
"url": f"{shop['url']}{page}",
"format": "webp",
"width": 1280,
"full_page": True,
"block_cookies": True,
"stealth": True,
"response_type": "json",
},
headers={"Authorization": f"Bearer {SR_KEY}"},
)
date = datetime.date.today().isoformat()
slug = page.strip("/").replace("/", "-") or "homepage"
print(f"{date} {shop['name']} {slug}: {response.json()['screenshot_url']}")
Run this daily via cron or a task scheduler. Over a month, you build a visual timeline of competitor storefront changes: product launches, pricing adjustments, seasonal redesigns, promotional banners. Marketing teams use this to time their own campaigns relative to competitor activity.
Headless commerce: Hydrogen and custom storefronts
Shopify Hydrogen stores are React-based single-page applications. They render on the client side (or through server-side rendering on Oxygen/Vercel). The HTML that arrives from the server is a shell. The actual product content appears after JavaScript executes and React hydrates the page.
Can you screenshot a Hydrogen store the same way as a Liquid theme? Yes, because the API runs a full browser. But the timing is different. For Hydrogen stores, increase the delay to 3000ms or use wait_for_selector to target a specific product element:
curl "https://api.screenshotrun.com/v1/screenshots/capture?\
url=https://hydrogen-store.com/products/limited-edition&\
format=png&\
width=1280&\
wait_for_selector=.product-price&\
block_cookies=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
--output hydrogen-product.png
The wait_for_selector=.product-price approach is more reliable than a fixed delay. The capture fires as soon as the price element exists in the DOM. No guessing how long hydration takes on a given page. That said, the right delay value is always somewhat of a guess with SPAs. A page that renders in 1.5 seconds on your machine might take 3 seconds from a datacenter on a different continent.
Campaign and promotion archiving
Black Friday banners disappear after Black Friday. Flash sale pages get taken down. Seasonal collection designs rotate out. Once the campaign ends, the visual record is gone unless someone saved it. The Wayback Machine is unreliable for JavaScript-heavy Shopify stores; it often captures the page before React or Liquid sections finish rendering.
Schedule captures at campaign launch, at peak traffic, at takedown. Use certified=true for a SHA-256 hash and timestamp on each capture. Useful for MAP (Minimum Advertised Price) compliance disputes or brand protection cases where you need to prove what a store displayed on a specific date.
curl "https://api.screenshotrun.com/v1/screenshots/capture?\
url=https://your-store.com/collections/black-friday&\
format=png&\
width=1280&\
full_page=true&\
block_cookies=true&\
certified=true&\
response_type=json" \
-H "Authorization: Bearer YOUR_API_KEY"
The JSON response includes the certification object with the hash, the timestamp, the original parameters. Store it alongside the screenshot for a verifiable record.
Parameters that matter most for Shopify stores
| Parameter | Why it matters for Shopify | Typical value |
|---|---|---|
full_page |
Product pages are long: reviews, related items, trust badges sit below the fold | true |
delay |
Shopify themes lazy-load images via Intersection Observer; needs time to trigger | 2000 |
block_cookies |
EU-targeted stores show GDPR consent banners that cover the viewport | true |
geolocation |
Shopify Markets serves different prices and currency by country | us, de, gb |
stealth |
Third-party bot protection apps (Sentri, GeoFraudShield) block datacenter IPs | true |
device |
Mobile commerce is 70%+ of Shopify traffic; capture both viewports | desktop, mobile |
cookies |
Password-protected development stores need the storefront_digest cookie | Session cookie value |
wait_for_selector |
Hydrogen/headless stores need React hydration to complete before capture | .product-price |
certified |
MAP compliance and brand protection require timestamped evidence | true |
block_ads |
Shopify apps inject pop-ups (email capture, upsells) that obscure content | true |
css |
Hide store-specific overlays like countdown timers or announcement bars that cause noise in QA comparisons | .announcement-bar { display: none; } |
For the full parameter reference, see the feature pages. Shopify screenshot API workflows pair well with Node.js for webhook handlers and Python for batch scripts. If you prefer no-code automation, connect Shopify events to the API through Zapier, Make, n8n, or Pipedream. Sign up free to start capturing Shopify pages programmatically. Every parameter listed above works on the free tier — 200 captures a month, no card required.
FAQ#
Submit the storefront password form manually to get the storefront_digest cookie value. Pass it in the cookies parameter of your API request. The API will use that cookie to bypass the password page. If the store owner changes the password, the cookie expires and you need to regenerate it.
Most Shopify themes use lazy loading with Intersection Observer. Images below the viewport only load when scrolled into view. Add full_page=true with delay=2000 to your request. The API scrolls the page first, triggering all lazy-load observers, then waits for images to finish loading before capture.
Yes. Shopify Markets adjusts prices and currency based on visitor location. Add the geolocation parameter (e.g., geolocation=de for Germany) to route the capture through a local IP. Shopify Markets responds with the localized pricing for that region.
Register a Shopify webhook for the products/update event and point it at your server. When Shopify fires the webhook, your handler calls the ScreenshotRun API with the product URL. Verify the HMAC signature in the x-shopify-hmac-sha256 header to ensure the request is authentic.
Yes. The API runs a full browser that executes JavaScript and waits for React hydration. For Hydrogen stores, use wait_for_selector to target a specific element like .product-price instead of relying on a fixed delay. The capture fires as soon as that element appears in the DOM.
Shopify itself does not run Cloudflare or similar WAF in front of storefronts. Some merchants install third-party protection apps like Sentri or GeoFraudShield. Adding stealth=true to your request makes the headless browser appear as a regular desktop visitor, which bypasses most third-party bot detection.