How to Generate Device Frame Mockups with an API
You need an iPhone screenshot mockup for your landing page. Or a MacBook frame around your app's dashboard for a pitch deck. Maybe you're generating hundreds of app store preview images programmatically.
Whatever the reason, manually opening Figma and placing screenshots into device frames doesn't scale. In this guide, we'll build an automated pipeline that captures a website screenshot and wraps it in a realistic device frame - all through API calls.
Why Automate Device Frame Mockups?
Device mockups are everywhere in SaaS marketing: landing pages, social media posts, app store listings, investor decks. The manual process looks something like this:
- Take a screenshot or screen recording
- Open a design tool (Figma, Sketch, Photoshop)
- Find a device frame template
- Resize and position the screenshot
- Export the final image
That's fine for one image. But when you need to generate mockups for 50 different pages, update them every time your UI changes, or create them on the fly for user-generated content, you need automation.
Common use cases for automated device mockups:
- SaaS landing pages - Show your product in realistic device contexts
- App store screenshots - Generate preview images for multiple device sizes
- Social media cards - Create shareable images with device frames
- Client reports - Show responsive designs across devices
- Documentation - Illustrate mobile vs. desktop experiences
Step 1: Capture the Screenshot
Before you can frame it, you need a clean screenshot at the right resolution. The key is matching the viewport size to the target device's screen resolution.
Here are the viewport sizes for popular devices:
- iPhone 15 Pro: 393 x 852
- iPhone 15 Pro Max: 430 x 932
- Samsung Galaxy S24: 360 x 780
- MacBook Pro 14": 1512 x 982
- Generic browser: 1440 x 900
Using GrabShot's screenshot API, you can capture at any viewport size:
curl
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=393&height=852&fullPage=false" \
-H "X-Api-Key: YOUR_API_KEY" \
-o iphone-screenshot.png
Node.js
const fetch = require('node-fetch');
const fs = require('fs');
async function captureForDevice(url, device) {
const viewports = {
'iphone-15-pro': { width: 393, height: 852 },
'iphone-15-max': { width: 430, height: 932 },
'galaxy-s24': { width: 360, height: 780 },
'macbook-pro-14': { width: 1512, height: 982 },
'browser-1440': { width: 1440, height: 900 },
};
const vp = viewports[device];
if (!vp) throw new Error(`Unknown device: ${device}`);
const params = new URLSearchParams({
url,
width: vp.width,
height: vp.height,
fullPage: 'false',
});
const res = await fetch(
`https://grabshot.dev/api/screenshot?${params}`,
{ headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
);
const buffer = Buffer.from(await res.arrayBuffer());
const filename = `${device}-screenshot.png`;
fs.writeFileSync(filename, buffer);
console.log(`Saved ${filename} (${buffer.length} bytes)`);
return filename;
}
// Capture for all devices at once
const devices = ['iphone-15-pro', 'macbook-pro-14', 'browser-1440'];
for (const device of devices) {
await captureForDevice('https://your-app.com', device);
}
Python
import requests
VIEWPORTS = {
"iphone-15-pro": (393, 852),
"iphone-15-max": (430, 932),
"galaxy-s24": (360, 780),
"macbook-pro-14": (1512, 982),
"browser-1440": (1440, 900),
}
def capture_for_device(url: str, device: str, api_key: str) -> str:
w, h = VIEWPORTS[device]
resp = requests.get(
"https://grabshot.dev/api/screenshot",
params={"url": url, "width": w, "height": h, "fullPage": "false"},
headers={"X-Api-Key": api_key},
)
resp.raise_for_status()
filename = f"{device}-screenshot.png"
with open(filename, "wb") as f:
f.write(resp.content)
print(f"Saved {filename} ({len(resp.content)} bytes)")
return filename
# Generate mockups for multiple devices
for device in ["iphone-15-pro", "macbook-pro-14"]:
capture_for_device("https://your-app.com", device, "YOUR_API_KEY")
Step 2: Add the Device Frame
Once you have a screenshot at the correct resolution, you need to composite it into a device frame. There are two main approaches:
Option A: Sharp (Node.js)
Sharp is the fastest image processing library for Node.js. You'll need a transparent PNG of the device frame with a cutout where the screen goes.
const sharp = require('sharp');
async function addDeviceFrame(screenshotPath, framePath, outputPath, screenPosition) {
// screenPosition: { left, top, width, height } - where the screen sits in the frame
const screenshot = await sharp(screenshotPath)
.resize(screenPosition.width, screenPosition.height, { fit: 'fill' })
.toBuffer();
// Composite: frame on top of screenshot positioned correctly
const frame = sharp(framePath);
const frameMeta = await frame.metadata();
await sharp({
create: {
width: frameMeta.width,
height: frameMeta.height,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
.composite([
{ input: screenshot, left: screenPosition.left, top: screenPosition.top },
{ input: framePath, left: 0, top: 0 },
])
.png()
.toFile(outputPath);
console.log(`Mockup saved to ${outputPath}`);
}
// Example: iPhone 15 Pro frame
addDeviceFrame('iphone-screenshot.png', 'frames/iphone-15-pro.png', 'mockup.png', {
left: 18,
top: 18,
width: 393,
height: 852,
});
Option B: Pillow (Python)
from PIL import Image
def add_device_frame(screenshot_path, frame_path, output_path, screen_box):
"""
screen_box: (left, top, width, height) where the screen sits in the frame
"""
frame = Image.open(frame_path).convert("RGBA")
screenshot = Image.open(screenshot_path).convert("RGBA")
left, top, width, height = screen_box
screenshot = screenshot.resize((width, height), Image.LANCZOS)
# Create canvas, paste screenshot, then frame on top
canvas = Image.new("RGBA", frame.size, (0, 0, 0, 0))
canvas.paste(screenshot, (left, top))
canvas = Image.alpha_composite(canvas, frame)
canvas.save(output_path)
print(f"Mockup saved to {output_path}")
add_device_frame(
"iphone-screenshot.png",
"frames/iphone-15-pro.png",
"mockup.png",
(18, 18, 393, 852),
)
Step 3: Where to Get Device Frame Assets
You need high-quality transparent PNGs of device frames. Here are reliable sources:
- Apple Design Resources - Official iPhone and Mac frames. Free, high quality.
- Meta Design Resources - Cross-platform device frames.
- DeviceFrames.com - Large collection, some free.
- Roll your own - A simple CSS border-radius + shadow can approximate a browser frame without any assets.
For browser frames specifically, you don't even need an image. You can generate one with pure code:
const sharp = require('sharp');
async function addBrowserFrame(screenshotPath, outputPath, title = '') {
const screenshot = sharp(screenshotPath);
const meta = await screenshot.metadata();
const barHeight = 40;
const padding = 2;
const totalWidth = meta.width + padding * 2;
const totalHeight = meta.height + barHeight + padding;
// Create a simple browser chrome with SVG
const browserChrome = Buffer.from(`
`);
const screenshotBuf = await screenshot.toBuffer();
await sharp(browserChrome)
.composite([
{ input: screenshotBuf, left: padding, top: barHeight },
])
.png()
.toFile(outputPath);
console.log(`Browser mockup saved to ${outputPath}`);
}
addBrowserFrame('browser-screenshot.png', 'browser-mockup.png', 'your-app.com');
Putting It All Together: Full Pipeline
Here's a complete Node.js script that captures a URL across multiple devices and generates framed mockups:
const fetch = require('node-fetch');
const sharp = require('sharp');
const fs = require('fs');
const API_KEY = process.env.GRABSHOT_API_KEY;
const API_URL = 'https://grabshot.dev/api/screenshot';
const DEVICES = {
'iphone-15-pro': {
viewport: { width: 393, height: 852 },
frame: 'frames/iphone-15-pro.png',
screen: { left: 18, top: 18, width: 393, height: 852 },
},
'browser': {
viewport: { width: 1440, height: 900 },
frame: null, // use CSS browser frame
screen: null,
},
};
async function generateMockups(url) {
const results = [];
for (const [name, config] of Object.entries(DEVICES)) {
// 1. Capture screenshot
const params = new URLSearchParams({
url,
width: config.viewport.width,
height: config.viewport.height,
fullPage: 'false',
});
const res = await fetch(`${API_URL}?${params}`, {
headers: { 'X-Api-Key': API_KEY },
});
const screenshotBuf = Buffer.from(await res.arrayBuffer());
const screenshotPath = `temp-${name}.png`;
fs.writeFileSync(screenshotPath, screenshotBuf);
// 2. Add frame
const outputPath = `mockup-${name}.png`;
if (config.frame) {
// Device frame compositing
const screenshot = await sharp(screenshotPath)
.resize(config.screen.width, config.screen.height, { fit: 'fill' })
.toBuffer();
const frameMeta = await sharp(config.frame).metadata();
await sharp({
create: {
width: frameMeta.width,
height: frameMeta.height,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
.composite([
{ input: screenshot, left: config.screen.left, top: config.screen.top },
{ input: config.frame, left: 0, top: 0 },
])
.png()
.toFile(outputPath);
} else {
// Browser frame (generated)
await addBrowserFrame(screenshotPath, outputPath, new URL(url).hostname);
}
results.push({ device: name, path: outputPath });
fs.unlinkSync(screenshotPath); // cleanup temp file
}
return results;
}
// Usage
generateMockups('https://your-app.com').then(console.log);
Tips for Better Mockups
- Match the device pixel ratio. iPhones render at 3x. If you want a retina-quality mockup, capture at 1179x2556 (3x of 393x852) and downscale after framing.
- Add shadows. A subtle drop shadow under the device frame makes it look much more realistic. Sharp's
extend+ a blurred dark rectangle works well. - Use a background. Place the framed device on a gradient or lifestyle photo instead of a white void.
- Wait for fonts. If the target page uses web fonts, add a delay to your screenshot capture (GrabShot supports a
delayparameter) so fonts render before capture. - Hide cookie banners. Use a custom CSS injection to hide popups. GrabShot's
cssparameter lets you inject styles like.cookie-banner { display: none !important; }.
Need screenshots at scale?
GrabShot captures pixel-perfect screenshots at any viewport size. 25 free captures per month, no credit card required.
Try It FreeWhen to Use This vs. a Mockup Tool
This API-driven approach is best when you need automation. If you're generating one mockup for a blog post, Figma or Shots.so is faster. But if you're building any of these, an API pipeline wins:
- A SaaS that shows users their site in device frames
- Automated app store screenshot generation in CI/CD
- Marketing pages that always show the latest UI
- Client deliverables at scale (agencies)
The code above is a starting point. In production, you'd add caching (don't re-capture if the page hasn't changed), queue management for bulk jobs, and CDN storage for the output images.
Wrapping Up
Device frame mockups don't need to be a manual design task. With a screenshot API and an image processing library, you can build a pipeline that captures any URL and wraps it in realistic device frames - iPhone, Android, MacBook, or a clean browser window.
The full pipeline: capture at the right viewport size, composite onto a device frame asset, and export. Whether you do it in Node.js with Sharp or Python with Pillow, it's the same pattern. Capture, composite, ship.