Mobile screenshots

To capture what a page looks like on a phone or tablet, you have two tools: a device preset that sets everything in one word, or a custom viewport where you set each property. This guide explains which to use and why a narrow viewport alone is often not enough. It is part of the capture guides.

The minimal request

The requests below need an API key in RS_API_KEY (get one free).

GET, with a device preset:

curl -G "https://api.renderscreenshot.com/v1/screenshot" \
  -H "Authorization: Bearer $RS_API_KEY" \
  --data-urlencode "url=https://example.com" \
  -d device=iphone_14 \
  -o iphone.png

POST, with the same preset:

curl -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "device": "iphone_14"
  }' \
  -o iphone.png

The response is a 1170 x 2532 PNG: a 390 x 844 viewport at a device scale of 3. In a POST body, keep device at the top level next to url. The devices reference explains why a nested viewport.device is not used.

Three ways to get a mobile capture

Approach You set Good for
device preset One id, for example iphone_14, pixel_7 or ipad_air Realistic captures of a known device
preset: mobile or mobile_landscape One preset that also sets format and cache defaults Quick portrait or landscape phone shots
Custom viewport width, height, scale, mobile, plus browser.user_agent A device that is not in the list, or exact control for tests

A device preset sets width, height, scale, mobile and the user agent. Explicit viewport values win over the device, so for landscape send the device together with a swapped viewport.width and viewport.height.

Why width alone is not enough

Many teams start with width: 390 and get a desktop page squeezed into a narrow window. That happens because three separate things make a browser behave like a phone:

  1. The layout viewport. A real phone honours the page's viewport meta tag, <meta name="viewport" content="width=device-width">. Chrome applies that tag only in mobile emulation, which is viewport.mobile: true. Without it, a page that has no tag still lays out at a desktop width.
  2. Input capabilities. Media queries such as (hover: none) and (pointer: coarse) describe a touch screen. The renderer does not emulate touch today, for device presets or custom viewports, so a site that shows its mobile menu or swipe controls only after a touch check renders its pointer version.
  3. The user agent. Some sites choose the mobile template on the server by reading the User-Agent header. A narrow window with a desktop user agent gets the desktop HTML. Device presets send that device's user agent; with a custom viewport, set browser.user_agent yourself. The custom user agent guide shows the effect.

A device preset handles the first and the third, so start there. Switch to a custom viewport only when you need a size that is not in the list.

Device presets

Get the current list from the API rather than from a copy in your code:

curl https://api.renderscreenshot.com/v1/devices \
  -H "Authorization: Bearer $RS_API_KEY"

Each entry has an id, a name, a viewport (width and height in CSS pixels, device_scale_factor, is_mobile, has_touch) and a user_agent. A selection:

Device id Viewport (CSS px) Scale Output image (px)
iphone_se 375 x 667 2 750 x 1334
iphone_14 390 x 844 3 1170 x 2532
pixel_7_pro 412 x 892 3.5 1442 x 3122
ipad_air 820 x 1180 2 1640 x 2360
ipad_pro_12 1024 x 1366 2 2048 x 2732

The output size is always viewport times scale. A large scale makes large files: see retina screenshots for the measured cost, and use viewport.scale: 1 together with a device-sized custom viewport if you only need the layout.

An unknown device id fails with a validation_error (HTTP 400, code invalid_device) before any rendering, so a typo never uses a credit.

Custom viewport

For a device that is not in the list, set the properties yourself:

{
  "url": "https://example.com",
  "viewport": {
    "width": 360,
    "height": 780,
    "scale": 3,
    "mobile": true
  },
  "browser": {
    "user_agent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36"
  }
}
Parameter Default Limits Effect
viewport.width 1280 1 to 7,680 Layout width in CSS pixels
viewport.height 800 1 to 4,320 Visible height; also the image height in viewport mode
viewport.scale 1 1 to 3 Device pixel ratio; multiplies output pixels
viewport.mobile false Applies the meta viewport tag, as a phone does
browser.user_agent browser default Makes the server send its mobile HTML

viewport.touch turns on touch support, and device presets set it for you. viewport.landscape only sets the browser's landscape flag and does not swap the width and height, so set the orientation with width and height.

Custom scale accepts 1 to 3. Device presets can use their real ratio, for example 3.5 for pixel_7_pro.

Full-page mobile captures

Mobile pages are long. Combine a device with full-page capture to get the whole scroll height at the device width:

{
  "url": "https://example.com/blog/post",
  "device": "iphone_14",
  "capture": { "mode": "full_page" }
}

At scale 3, a 6,000 px tall mobile page becomes an 18,000 px tall image. If the image is for a web page or for a vision model, set viewport.scale: 1 next to the device, or resize the image on your side. The full-page guide covers scroll.max_height.

SDK examples

Device preset first, then a custom viewport.

Node.js

import { writeFile } from 'node:fs/promises';
import { Client, TakeOptions } from 'renderscreenshot';

const client = new Client(process.env.RS_API_KEY);

const phone = await client.take(TakeOptions.url('https://example.com').device('iphone_14'));
await writeFile('iphone.png', phone);

const custom = await client.take(
  TakeOptions.url('https://example.com').width(360).height(780).scale(3).mobile()
);
await writeFile('custom.png', custom);

Python

import os
from renderscreenshot import Client, TakeOptions

client = Client(os.environ['RS_API_KEY'])

phone = client.take(TakeOptions.url('https://example.com').device('iphone_14'))
custom = client.take(
    TakeOptions.url('https://example.com').width(360).height(780).scale(3).mobile()
)
with open('iphone.png', 'wb') as f:
    f.write(phone)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))

phone = client.take(RenderScreenshot::TakeOptions.url('https://example.com').device('iphone_14'))
custom = client.take(
  RenderScreenshot::TakeOptions.url('https://example.com').width(360).height(780).scale(3).mobile
)
File.binwrite('iphone.png', phone)

PHP

use RenderScreenshot\Client;
use RenderScreenshot\TakeOptions;

$client = new Client(getenv('RS_API_KEY'));

$phone = $client->take(TakeOptions::url('https://example.com')->device('iphone_14'));
$custom = $client->take(
    TakeOptions::url('https://example.com')->width(360)->height(780)->scale(3)->mobile()
);
file_put_contents('iphone.png', $phone);

Java

Client client = new Client(System.getenv("RS_API_KEY"));

byte[] phone = client.take(TakeOptions.url("https://example.com").device("iphone_14"));
byte[] custom = client.take(
    TakeOptions.url("https://example.com").width(360).height(780).scale(3.0).mobile()
);
Files.write(Path.of("iphone.png"), phone);

Go

phone, err := client.Take(ctx, rs.URL("https://example.com").Device("iphone_14"))
if err != nil {
    log.Fatal(err)
}
custom, err := client.Take(ctx,
    rs.URL("https://example.com").Width(360).Height(780).Scale(3).Mobile(),
)
if err != nil {
    log.Fatal(err)
}
os.WriteFile("iphone.png", phone, 0o644)
os.WriteFile("custom.png", custom, 0o644)

Troubleshooting

Symptom Likely cause Fix
Desktop layout in a narrow image mobile not set, so the meta viewport tag is ignored Use a device preset or set viewport.mobile: true
Mobile layout, but tiny text The page has no meta viewport tag Expected for such pages; this is what a phone shows too
Desktop HTML even with mobile: true The server picks the template from the user agent Use a device preset, or set browser.user_agent to a mobile string
Hamburger menu or swipe UI missing Site checks for touch support Touch is not emulated; capture the mobile menu with page.click on its button, or inject the class the site sets with page.scripts
App install banner or cookie sheet covers the page Mobile-only overlays block.cookie_banners: true, or page.hide with the banner selector
Image is huge Scale 3 times full page height Use viewport.scale: 1
validation_error naming the device Device id not in the list Check GET /v1/devices
Landscape needed Portrait is the default Swap viewport.width and viewport.height next to the device, or use preset: mobile_landscape

Related in this topic

Was this page helpful?