Microlink alternative for website screenshots: ScreenshotOne compared

Compare Microlink and ScreenshotOne for screenshot workflows. Map request options, handle different responses, and migrate image capture without breaking metadata.

Blog post5 min read

Written by

Dmytro Krasun

Updated on

A quick disclaimer: I run ScreenshotOne, so I’m not neutral. I’ll stick to differences you can check in both services’ docs, and I won’t claim one renders every site better or faster. Test that on your own URLs.

Microlink and ScreenshotOne overlap on screenshots, but they start from different places. Microlink is a URL-to-data API: by default it returns metadata (title, description, images, and so on), and screenshots and PDFs are add-ons. ScreenshotOne is a rendering API: you get an image or a PDF, with lots of control over how the page is loaded, and a few metadata fields if you ask for them.

So if your app mainly uses Microlink for screenshots, moving is fairly simple. If it relies on Microlink’s metadata and embeds, you might move only the screenshots and keep Microlink for the rest. That’s a perfectly reasonable setup.

What to check before you switch

If you need to…Compare
Save a PNG when a job finishesRaw image bytes vs. JSON with an image URL you then download
Build link cardsExactly which metadata fields your code reads, and what happens when they’re missing
Capture JavaScript-heavy pagesWaiting, scrolling, viewport, session, and blocking options, tested on your real URLs
Keep an archiveWhere the file is stored long-term and who controls retention
Process a queueConcurrency, rate limits, retries, timeouts, and cost at your volume

One successful request against a simple landing page won’t tell you much. Use a handful of the pages that give you trouble today.

Option mapping

For a full-page PNG at a 1280 × 800 viewport:

What you wantMicrolinkScreenshotOne
Page URLurlurl
Take a screenshotscreenshot=trueCall /take
Full pagescreenshot.fullPage=truefull_page=true
PNGscreenshot.type=pngformat=png
Viewport widthviewport.width=1280viewport_width=1280
Viewport heightviewport.height=800viewport_height=800
Pixel densityviewport.deviceScaleFactor=1device_scale_factor=1

References: Microlink’s fullPage, type, and viewport docs, and ScreenshotOne’s options reference.

Matching the settings makes the comparison fair, but don’t expect identical pixels. Browser versions, fonts, when each service decides the page is “ready,” and how each one scrolls will all differ a little.

One script, both providers

This Node.js 22+ script takes the same screenshot with either provider and saves a PNG, so you can compare the results side by side.

For Microlink, it uses the Pro endpoint (https://pro.microlink.io) with an x-api-key header. The free endpoint is https://api.microlink.io. Don’t send your Pro key there. See Microlink authentication.

Set MICROLINK_API_KEY and/or SCREENSHOTONE_ACCESS_KEY, and save this as capture.mjs:

import { writeFile } from "node:fs/promises";
const [provider, inputUrl, outputPath = `${provider}.png`] = process.argv.slice(2);
if (!["microlink", "screenshotone"].includes(provider) || !inputUrl) {
throw new Error("Usage: node capture.mjs microlink|screenshotone URL [output.png]");
}
const target = new URL(inputUrl);
async function checkedFetch(url, options = {}) {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(120_000),
});
if (!response.ok) throw new Error(`Request failed: HTTP ${response.status}`);
return response;
}
async function captureWithMicrolink() {
const key = process.env.MICROLINK_API_KEY;
if (!key) throw new Error("Missing MICROLINK_API_KEY.");
const endpoint = new URL("https://pro.microlink.io");
endpoint.search = new URLSearchParams({
url: target.href,
screenshot: "true",
"screenshot.fullPage": "true",
"screenshot.type": "png",
"viewport.width": "1280",
"viewport.height": "800",
"viewport.deviceScaleFactor": "1",
}).toString();
// Microlink returns JSON with an image URL, so this takes two requests.
const response = await checkedFetch(endpoint, { headers: { "x-api-key": key } });
const result = await response.json();
const imageUrl = result.data?.screenshot?.url;
if (result.status !== "success" || typeof imageUrl !== "string") {
throw new Error("Microlink didn't return a screenshot.");
}
return checkedFetch(imageUrl);
}
async function captureWithScreenshotOne() {
const key = process.env.SCREENSHOTONE_ACCESS_KEY;
if (!key) throw new Error("Missing SCREENSHOTONE_ACCESS_KEY.");
// ScreenshotOne returns the image bytes directly.
return checkedFetch("https://api.screenshotone.com/take", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Access-Key": key },
body: JSON.stringify({
url: target.href,
format: "png",
full_page: true,
viewport_width: 1280,
viewport_height: 800,
device_scale_factor: 1,
}),
});
}
const imageResponse = provider === "microlink"
? await captureWithMicrolink()
: await captureWithScreenshotOne();
const image = Buffer.from(await imageResponse.arrayBuffer());
const pngHeader = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
if (!image.subarray(0, 8).equals(pngHeader)) {
throw new Error("Didn't get a PNG back; nothing was written.");
}
await writeFile(outputPath, image);
console.log(`Saved ${outputPath} (${image.length} bytes)`);

Run it once per provider against the same URL:

Terminal window
node capture.mjs microlink 'https://example.com/' microlink.png
node capture.mjs screenshotone 'https://example.com/' screenshotone.png

Both requests count against your plans. Open the two files at full size and compare them, preferably on harder pages than example.com. This script isn’t a speed or price benchmark.

The response shape is the main code change

Microlink’s screenshot response is JSON, with the image URL in data.screenshot.url. ScreenshotOne’s default response is the image. Anywhere your code calls response.json() and reads data.screenshot.url will break after the switch.

If the rest of your app expects a URL, ScreenshotOne can return JSON too: set response_type=json and read screenshot_url. That URL is temporary and lasts up to four hours by default, so copy anything you want to keep into your own storage. Don’t store it as a permanent link.

Either way, I’d put provider-specific code behind a small adapter that returns something like { file, capturedAt, sourceUrl }. Then the switch happens in one file, and you can roll it out gradually.

Don’t lose your metadata

Before migrating, search your code for every field you read from Microlink’s response. A screenshot might be used in one component while the title, description, or logo is used in three others.

ScreenshotOne can return some metadata if you ask: metadata_page_title, metadata_icon, and metadata_open_graph (see metadata options). They’re opt-in, the response is structured differently, and they don’t cover everything Microlink extracts. If you rely on its embeds or its wider extraction, keep Microlink for that part.

Whatever you use, plan for missing titles and images, and keep the screenshot separate from the site’s own Open Graph image. They’re different things.

The Node.js screenshot and metadata guide provides a complete example that saves the image and handles missing titles, favicons, and Open Graph fields.

Test on real pages

Pick a small set that covers what usually breaks:

  • A simple landing page, to check dimensions and basic output
  • A long page with lazy-loaded images, to check the bottom of the capture
  • A JavaScript app, to check when it decides the page is ready
  • A logged-in page, if you pass sessions
  • A page with cookie banners, chat widgets, or other overlays

Record successful captures, incomplete renders, latency, and retries. Compare like with like on caching: a cached response from one provider against a fresh render from the other isn’t a fair test.

Before moving a production queue, check ScreenshotOne’s plans and Microlink’s for your actual volume, including concurrency, caching, retries, storage, and any features you’d use. Per-request price is only part of the cost.

Then send a small share of traffic through the new adapter, review the output, and keep rollback simple until you’re happy. If you’re still choosing between vendors, here’s our roundup of screenshot APIs.

Read more Screenshot rendering

Interviews, tips, guides, industry best practices, and news.

View all posts
How to take website screenshots with Go

How to take website screenshots with Go

The article examines how you can take screenshots of any URL with Go (a.k.a. Golang) by using Selenium, Puppeteer alternatives, Playwright, or screenshot API as a service.

Read more
How to Build a Screenshot API

How to Build a Screenshot API

Learn how to build a screenshot API from scratch. Render website screenshots, capture full pages, block cookie banners, trigger lazy-loaded content, upload results, and prepare the service for deployment.

Read more

Automate website screenshots

Exhaustive documentation, ready SDKs, no-code tools, and other automation to help you render website screenshots and outsource all the boring work related to that to us.