RenderScreenshot Node.js SDK: Install and Tested Examples
Install the renderscreenshot npm package and take screenshots with Client and TakeOptions. Tested examples, error handling and the known issues in 1.1.0.
The official Node.js SDK for RenderScreenshot is the renderscreenshot package on npm. It wraps the screenshot API in a Client class and a fluent TakeOptions builder, with TypeScript types included. This post shows how to install it, five examples we tested against the current release, and the known issues in version 1.1.0 with a workaround for each. For the full method list, see the Node.js SDK reference; for a framework-by-framework overview, see the Node.js screenshot API page.
Tested on October 15, 2026 with Node.js 22.18.0 and [email protected], against a local mock of POST /v1/screenshot that returns the API's real response shapes. Each example's request body was checked against the parameters the API accepts.
Install
npm install [email protected]
The package needs Node.js 20 or later. It ships ESM and CommonJS builds and its own type definitions. Keep your API key in an environment variable; all examples read RS_API_KEY.
1. Take a screenshot
// og-card.mjs import { writeFile } from 'node:fs/promises'; import { Client, TakeOptions } from 'renderscreenshot'; const client = new Client(process.env.RS_API_KEY); const image = await client.take( TakeOptions.url('https://example.com').preset('og_card'), ); await writeFile('og-card.png', image);
take() returns a Buffer with the image. The og_card preset renders a 1200x630 PNG; the other presets and their sizes are in the presets reference.
2. Set the viewport or emulate a device
// Retina desktop capture: 1440x900 viewport at device scale factor 2 const desktop = await client.take( TakeOptions.url('https://example.com').width(1440).height(900).scale(2), ); // Phone capture with a device preset const phone = await client.take( TakeOptions.url('https://example.com').device('iphone_14_pro'), );
width, height, scale and mobile are sent in the nested viewport object, and device at the top level, which is what the API expects.
3. Handle errors by HTTP status
import { Client, TakeOptions, RenderScreenshotError } from 'renderscreenshot'; const client = new Client(process.env.RS_API_KEY); try { const image = await client.take(TakeOptions.url('https://example.com')); console.log(`Got ${image.length} bytes`); } catch (err) { if (!(err instanceof RenderScreenshotError)) throw err; if (err.httpStatus === 429) { console.error(`Rate limited. Retry in ${err.retryAfter ?? 60} s.`); } else if (err.httpStatus === 401 || err.httpStatus === 403) { console.error('Check RS_API_KEY.'); } else { console.error(`Request failed with HTTP ${err.httpStatus}.`); } process.exitCode = 1; }
Branch on err.httpStatus, not err.code, in 1.1.0 (see the known issues below). retryAfter comes from the Retry-After header and is correct. Our load testing guide shows what rate limits look like under traffic.
4. Options the 1.1.0 builder does not send correctly
For full-page captures, output format, wait conditions and blocking, call the API with fetch and the nested JSON body from the POST endpoint reference until the SDK fix ships:
// full-page.mjs import { writeFile } from 'node:fs/promises'; const res = await fetch('https://api.renderscreenshot.com/v1/screenshot', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://example.com', capture: { mode: 'full_page' }, output: { format: 'webp', quality: 80 }, wait: { until: 'networkidle' }, block: { cookie_banners: true, ads: true }, }), }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); await writeFile('full-page.webp', Buffer.from(await res.arrayBuffer())); console.log('CDN copy:', res.headers.get('x-cache-url'));
The X-Cache-URL header holds a public CDN link to the same image, valid until the cache expires. The other response headers are listed in the headers reference.
5. Signed URLs for img and og:image tags
To put a screenshot URL in public HTML, sign it on your server with your rs_pub_ key ID and rs_secret_ secret. Use the signing function from the signed URL documentation, which is tested against the API's verifier:
import { createHmac } from 'node:crypto'; function encode(value) { return encodeURIComponent(String(value)) .replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()) .replace(/%20/g, '+'); } export function signedScreenshotUrl(params, { keyId, secret, expiresAt }) { const fields = { ...params, key_id: keyId, expires: Math.floor(expiresAt.getTime() / 1000) }; const query = Object.entries(fields) .map(([k, v]) => `${encode(k)}=${encode(v)}`) .sort() .join('&'); const signature = createHmac('sha256', secret).update(query).digest('hex'); return `https://api.renderscreenshot.com/v1/screenshot?${query}&signature=${signature}`; }
Expiry is required, with a maximum of 30 days. Do not use client.generateUrl() from 1.1.0 for this; see below.
Known issues in 1.1.0
We found these while writing the tests for this post. Each one is reproducible with a mock server that records what the SDK sends.
| Area | What 1.1.0 does | Effect | Use instead |
|---|---|---|---|
| Builder options beyond the viewport | Sends full_page, format, quality, wait_for, block_*, dark_mode and similar as flat top-level keys in the POST body |
The POST endpoint reads these from nested objects (capture, output, wait, block, browser), so they are ignored and you get the defaults |
fetch with the nested body (example 4) |
takeJson() |
Adds response_type: 'json' to the body |
The API returns JSON only when the request has an Accept: application/json header, so the call receives an image and fails to parse it |
fetch with Accept: application/json |
generateUrl() |
Signs with the client's own key, omits key_id, and does not form-encode values |
The URL does not pass signature verification | The signing function in example 5 |
| Error codes | Reads code from the top of the error body, but the API nests it under error |
err.code is internal_error and err.retryable is true for every API error |
err.httpStatus and err.retryAfter (example 3) |
presets() |
Typed as an array | The API returns { "presets": [...] } |
Read the presets property |
What works as documented in 1.1.0: the Client constructor and its baseUrl and timeout options, take() with url, html, preset, device, width, height, scale and mobile, and the httpStatus and retryAfter fields of RenderScreenshotError.
A fixed release is the next item on the SDK's list. This post will be updated with the version number, and the issues table will go away, when it ships.
How the SDK fits the architecture
The SDK is a thin HTTPS client. Each take() is one POST /v1/screenshot to our Rails API, which checks the key, expands presets, looks up the cache and counts credits, then asks a Cloudflare Worker to drive the browser. The image lands in R2 and comes back in the response. The full path, with the design decisions behind it, is in how we built RenderScreenshot.
Get a key
The examples above need an API key. Create a free account, copy the rs_live_ key from the dashboard into RS_API_KEY, and run example 1.
Related in this topic
- Node.js Screenshot API: TypeScript SDK and Examples Take website screenshots from Node.js with the renderscreenshot npm package: TypeScript, streams to disk, Express and Next.js routes, vitest mocks.
- Next.js Screenshot API: Server Actions and Signed Images Capture web pages from Next.js 16: a Server Action with useActionState, signed thumbnail URLs in Server Components, maxDuration on Vercel and vitest tests.
- Astro Screenshot API: Build-Time OG Cards and SSR Astro OG images with a screenshot API: render cards from HTML at build time with a hash cache, plus an on-demand endpoint and signed URLs for SSR pages.
- Express Screenshot API: A Streaming Proxy in 60 Lines Build an Express 5 screenshot service: a validated proxy that streams images from the API with cache headers, signed gallery URLs and supertest tests.
- Nuxt Screenshot API: Nitro Routes and useSeoMeta Nuxt 4 OG images from a screenshot API: a Nitro server route with binary-safe storage, useSeoMeta wiring, signed URLs, and when nuxt-og-image fits better.
- SvelteKit Screenshot API: +server.ts and Signed URLs SvelteKit OG images from a screenshot API: a +server.ts endpoint, Web Crypto signed URLs in a load function, and timeout notes for Node, Vercel and Cloudflare.