Pipedream is a workflow tool for developers: triggers and app actions in a UI, with a code step wherever you need one. There is no RenderScreenshot app in its catalog, and you do not need one. A Node.js code step with three inputs calls the API, returns a public image link, and saves the file in /tmp so that the next step (Slack, Google Drive, S3, an email) can attach it. This page gives you that step, a second step for lists of URLs, and the Pipedream-specific details: props, $.export, /tmp and what happens on an error. Other no-code tools are compared on the automation overview, and every integration is on the integrations hub.
Last tested: 2026-10-18 with Node 22.18. We ran both steps in a harness that calls
run()the way Pipedream does (props onthis, a$object withexport), against a local stub of our API that returns the documented JSON and error envelope. The tests covered a full-page capture, a bare domain (400 invalid_url), a page that times out (422 timeout), a wrong key (401 missing_credentials) and a two-URL batch. We did not run the steps inside a live Pipedream workflow for this page, so there are no Pipedream UI screenshots, and Pipedream does not offer a file export we could attach as a template.
What you can build
- Screenshot on form submit. An HTTP or Typeform trigger receives a website URL, the code step captures it, and a Slack step posts the image to your sales channel.
- A weekly visual record. A Schedule trigger runs on Mondays. The batch step captures your pricing page and the pricing pages of three competitors, and a Google Drive step saves each file.
Prerequisites
- A Pipedream account. Code steps are available on every plan.
- A RenderScreenshot key that starts with
rs_live_. Create a free account to get one. - In Pipedream, open Settings → Environment Variables and add
RS_API_KEYwith your key. Code steps read it asprocess.env.RS_API_KEY, and the value is not shown in step logs or exports.
Step by step
- Add your trigger and send a test event, so that later steps can reference its data.
- Click +, choose Run custom code, and keep the language as Node.js.
- Replace the code with the listing below and click Refresh fields. Pipedream reads the
propsand shows Page URL, Preset, Full page and Also save the file to /tmp as form fields. - Set Page URL to a value from the trigger, for example
{{steps.trigger.event.body.website}}. - Click Test. The step summary reads like "Captured https://example.com (1920x1080, 1 credit)", and the exports show
image_url,file_pathand the other fields. - In a later step, use
steps.<step_name>.$return_value.image_urlfor tools that take a link, orfile_pathfor steps that read a file.
The screenshot step
// Pipedream Node.js code step: capture one page with RenderScreenshot. // Needs the environment variable RS_API_KEY (rs_live_...) in Pipedream Settings → Environment Variables. import fs from 'node:fs/promises'; const API = 'https://api.renderscreenshot.com'; export default defineComponent({ props: { pageUrl: { type: 'string', label: 'Page URL', description: 'Must start with http:// or https://' }, preset: { type: 'string', label: 'Preset', default: 'desktop_hd', optional: true }, fullPage: { type: 'boolean', label: 'Full page', default: false, optional: true }, saveToTmp: { type: 'boolean', label: 'Also save the file to /tmp', default: true, optional: true }, }, async run({ steps, $ }) { const res = await fetch(`${API}/v1/screenshot`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json', // a JSON body with a CDN link instead of image bytes }, body: JSON.stringify({ url: this.pageUrl, preset: this.preset ?? 'desktop_hd', ...(this.fullPage && { capture: { mode: 'full_page' } }), block: { ads: true, cookie_banners: true }, cache: { available: true }, // a replayed event reuses the render for 0 credits }), signal: AbortSignal.timeout(60_000), }); const data = await res.json(); if (!res.ok) { const { code, message, retryable } = data.error; // Throwing marks the step as failed; the message shows in the event inspector. throw new Error(`${res.status} ${code} (retryable: ${retryable}): ${message}`); } let filePath = null; if (this.saveToTmp ?? true) { const img = await fetch(data.image.url); filePath = `/tmp/${data.id}.${data.image.format}`; await fs.writeFile(filePath, Buffer.from(await img.arrayBuffer())); } $.export('$summary', `Captured ${this.pageUrl} (${data.image.width}x${data.image.height}, ${data.usage.credits} credit)`); return { image_url: data.image.url, // public CDN link, valid until cache.expires_at expires_at: data.cache.expires_at, file_path: filePath, width: data.image.width, height: data.image.height, title: data.metadata.title, reused: data.cache.hit, credits_remaining: data.usage.remaining, }; }, });
Three choices in this step are deliberate:
- JSON, not bytes. With
Accept: application/jsonthe API answers with a CDN link, the size, the page title and the credit count. Step exports must be JSON, so a link is the right thing to return. - A file in
/tmp, too. Pipedream keeps/tmpfor the duration of the run, so a later upload step in the same run can readfile_path. Do not expect the file to still be there in a later run. - Errors throw. A thrown error marks the step as failed, stops the workflow and shows the API's code and
retryableflag in the event inspector.
The batch step, for lists
When the trigger gives you a list (a sheet, a scheduled set of pages), one batch request is faster and simpler than a loop of single calls. With 10 URLs or fewer, the batch endpoint answers in the same response:
// Pipedream Node.js code step: capture a list of pages in one batch request. // Up to 10 URLs return results in the same response; larger lists run asynchronously. const API = 'https://api.renderscreenshot.com'; export default defineComponent({ props: { urls: { type: 'string[]', label: 'Page URLs' }, preset: { type: 'string', label: 'Preset', default: 'og_card', optional: true }, }, async run({ $ }) { if (this.urls.length > 10) throw new Error('Send at most 10 URLs per step for a synchronous batch.'); const res = await fetch(`${API}/v1/batch`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ urls: this.urls, options: { preset: this.preset ?? 'og_card' } }), signal: AbortSignal.timeout(120_000), }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`); // Counts are top-level fields; each result has image (on success) or an error message. $.export('$summary', `${data.completed} of ${data.total} captured, ${data.usage.credits} credits`); return { images: data.results .filter((r) => r.status === 'completed') .map((r) => ({ url: r.url, image_url: r.image.image_url, width: r.image.width, height: r.image.height })), failed: data.results.filter((r) => r.status === 'failed').map((r) => ({ url: r.url, error: r.error })), }; }, });
Map Page URLs to an array from an earlier step. Failed pages do not fail the step. They come back in failed with the error message, so a later step can post them to Slack or retry only those URLs. Note that the image link of a batch result is image.image_url, not image.url as in the single-capture response. Your plan also caps the URLs per batch, and a longer list fails the whole request with 403 batch_size_exceeded; the rate limits page lists the cap for each plan. A batch counts as one request against the per-minute limit.
Parameter mapping
| Step input | API field | Example | Notes |
|---|---|---|---|
| Page URL | url |
https://example.com |
Must include http:// or https:// |
| Preset | preset |
desktop_hd, mobile, og_card |
Viewport and format in one value; see presets |
| Full page | capture.mode |
full_page |
Captures the whole scrollable page |
| (fixed) | block.ads, block.cookie_banners |
true |
Cleaner captures of marketing pages |
| (fixed) | cache.available |
true |
Replays reuse the render for 0 credits |
| Page URLs (batch) | urls |
array of strings | One request for the whole list |
To send another option, add it to the JSON body. Every field in the POST reference works the same way here.
Errors and retries in Pipedream
A failed step stops the workflow and the event shows as an error in the workflow's event history, where you can replay it after you fix the cause. If your plan includes automatic retries for errors, you can turn them on in the workflow's settings. Retries are safe for credits: a failed render costs nothing, and a replay of a render that worked returns the earlier result for 0 credits.
| Error in the event inspector | Cause | What to do |
|---|---|---|
400 invalid_url (retryable: false) |
The trigger sent example.com without a scheme |
Prefix https:// in the Page URL field |
401 missing_credentials (retryable: false) |
RS_API_KEY is missing or wrong |
Fix the environment variable; replay |
403 insufficient_credits |
The month's credits are used | Wait for the reset or change plan; see pricing |
422 timeout (retryable: true) |
The page did not finish loading | Replay once; add wait.delay or block cookie banners if it repeats |
429 too_many_requests (retryable: true) |
Too many requests this minute across all your keys | Replay after a minute; spread scheduled runs |
The error handling guide explains which codes are worth a retry and why.
Limits and costs
- Credits. One fresh render is one credit; failed renders and cache hits are free. The batch step uses one credit per page that rendered.
- Pipedream credits and time. Pipedream bills by compute time, and the step waits while the page renders. Pipedream also stops a workflow after its execution timeout, which you can raise in the workflow settings.
- Link lifetime.
image_urlstops working atexpires_at, 24 hours after the render by default. Save the file in the same run if you need it longer, or setcache.ttlup to 30 days. The caching docs have the details.
Troubleshooting
- The fields do not appear. Click Refresh fields after you paste the code; Pipedream reads
propsonly then. file_pathis missing in a later run./tmpis for the current run. Upload the file in the same workflow, or useimage_url.
If you prefer a visual node graph, the n8n workflow does the same with an importable file, and Zapier covers a Webhooks-only setup. Activepieces does the same with its HTTP piece.