Back to Blog
Guide February 26, 2026 · 4 min read

How to Screenshot Your App on 10 Different Devices in One Script

Capture your app on iPhone, iPad, MacBook, Android, and more in a single script — no device lab, no BrowserStack subscription, no Simulator. One API call per device, results in under 60 seconds.

Checking your app on multiple devices usually means: spinning up iOS Simulator, Android Emulator, resizing your browser, or paying for BrowserStack. None of these run headlessly or fit naturally into a CI pipeline.

Here's a script that captures your app on a full device matrix — phones, tablets, laptops — in parallel, under 60 seconds, with no emulators.

Available devices

// Run this to see the full list of 25+ supported device presets
const res = await fetch("https://pagebolt.dev/api/v1/list-devices", {
  headers: { "x-api-key": process.env.PAGEBOLT_API_KEY },
});
const { devices } = await res.json();
console.log(devices.map((d) => d.id).join("\n"));

Key presets:

iphone_14_pro       iphone_se
iphone_14_pro_max   ipad_pro_12_9
ipad_mini           galaxy_s23
galaxy_tab_s8       macbook_pro_16
macbook_air_m2      surface_pro_9

Full device matrix script

import fs from "fs/promises";
import path from "path";

const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;

const DEVICES = [
  { id: "iphone_se",          label: "iPhone SE (small)" },
  { id: "iphone_14_pro",      label: "iPhone 14 Pro" },
  { id: "iphone_14_pro_max",  label: "iPhone 14 Pro Max" },
  { id: "ipad_mini",          label: "iPad mini" },
  { id: "ipad_pro_12_9",      label: "iPad Pro 12.9" },
  { id: "galaxy_s23",         label: "Samsung Galaxy S23" },
  { id: "galaxy_tab_s8",      label: "Samsung Galaxy Tab S8" },
  { id: "macbook_air_m2",     label: "MacBook Air M2" },
  { id: "macbook_pro_16",     label: "MacBook Pro 16" },
  { id: "surface_pro_9",      label: "Surface Pro 9" },
];

const PAGES = [
  { name: "home",     url: "https://yourapp.com" },
  { name: "pricing",  url: "https://yourapp.com/pricing" },
  { name: "signin",   url: "https://yourapp.com/signin" },
];

async function screenshot(url, deviceId) {
  const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
    method: "POST",
    headers: {
      "x-api-key": PAGEBOLT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url,
      viewportDevice: deviceId,
      fullPage: false,
      blockBanners: true,
    }),
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  return Buffer.from(await res.arrayBuffer());
}

async function main() {
  const outDir = "device-screenshots";
  await fs.mkdir(outDir, { recursive: true });

  for (const page of PAGES) {
    const pageDir = path.join(outDir, page.name);
    await fs.mkdir(pageDir, { recursive: true });

    console.log(`\n${page.name} — capturing ${DEVICES.length} devices in parallel...`);

    const results = await Promise.allSettled(
      DEVICES.map(async (device) => {
        const image = await screenshot(page.url, device.id);
        const filename = `${device.id}.png`;
        await fs.writeFile(path.join(pageDir, filename), image);
        return { device, filename, bytes: image.length };
      })
    );

    for (const result of results) {
      if (result.status === "fulfilled") {
        const { device, bytes } = result.value;
        console.log(`  ✓ ${device.label} (${bytes} bytes)`);
      } else {
        console.error(`  ✗ ${result.reason}`);
      }
    }
  }

  console.log(`\nDone. Screenshots in ${outDir}/`);
}

main().catch(console.error);

Output structure:

device-screenshots/
  home/
    iphone_se.png
    iphone_14_pro.png
    iphone_14_pro_max.png
    ipad_mini.png
    ipad_pro_12_9.png
    galaxy_s23.png
    galaxy_tab_s8.png
    macbook_air_m2.png
    macbook_pro_16.png
    surface_pro_9.png
  pricing/
    ...
  signin/
    ...

Dark mode matrix

const MATRIX = DEVICES.flatMap((device) => [
  { device, darkMode: false, label: `${device.id}-light` },
  { device, darkMode: true,  label: `${device.id}-dark` },
]);

await Promise.allSettled(
  MATRIX.map(async ({ device, darkMode, label }) => {
    const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
      method: "POST",
      headers: { "x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({
        url: "https://yourapp.com",
        viewportDevice: device.id,
        darkMode,
        blockBanners: true,
      }),
    });
    const image = Buffer.from(await res.arrayBuffer());
    await fs.writeFile(path.join(outDir, `${label}.png`), image);
    console.log(`✓ ${label}`);
  })
);

GitHub Actions — run on every PR

name: Device matrix screenshots

on:
  pull_request:
    branches: [main]

jobs:
  device-matrix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Wait for preview
        run: |
          for i in $(seq 1 24); do
            [ "$(curl -s -o /dev/null -w '%{http_code}' '${{ vars.PREVIEW_URL }}')" = "200" ] && break
            sleep 5
          done

      - name: Screenshot device matrix
        env:
          PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
          BASE_URL: ${{ vars.PREVIEW_URL }}
        run: node scripts/device-matrix.js

      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        with:
          name: device-matrix-${{ github.run_id }}
          path: device-screenshots/

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## Device matrix screenshots\n\n10 devices × ${process.env.PAGE_COUNT || 3} pages captured. [Download artifacts](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`
            });

Check for responsive breakage automatically

Capture at specific widths to catch layout breakpoints:

const WIDTHS = [320, 375, 768, 1024, 1280, 1440, 1920];

await Promise.allSettled(
  WIDTHS.map(async (width) => {
    const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
      method: "POST",
      headers: { "x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({
        url: "https://yourapp.com",
        viewport: { width, height: 900 },
        blockBanners: true,
      }),
    });
    const image = Buffer.from(await res.arrayBuffer());
    await fs.writeFile(`screenshots/width-${width}.png`, image);
    console.log(`✓ ${width}px`);
  })
);

10 devices in parallel typically completes in 8–12 seconds. No emulator cold-start, no BrowserStack session limit, no Simulator installation.

Try it free

100 requests/month, no credit card required. OG images, screenshots, PDFs, and video — one API.

Get API Key — Free