I needed screenshots of a few web pages from Node-RED (a weekly snapshot of some status pages and a dashboard image for a Telegram bot), so here is the small flow I ended up with. It only uses core nodes: inject, function, http request, switch and file.
It calls a hosted screenshot API, CurlShot (disclosure: I build it; there is a free plan with 100 screenshots a month, no card), so there is no Chromium or Puppeteer to install on the Pi or in the container. The same shape works for any screenshot API that returns the image as the response body.
How it works
- The
injectnode setsmsg.urlto the page you want. - The
functionnode builds the query (format,full_page,block_cookie_banners,block_ads) and puts the key in anX-Access-Keyheader, read from the env varCURLSHOT_KEY. - The
http requestnode returns a binary buffer. - The
switchchecksstatusCode; a 200 goes to thefilenode, anything else (the API answers errors as JSON witherror_message) goes to debug.
Change format to pdf for a PDF of the page, or add viewport_device=iphone_15_pro for the mobile layout. Instead of the file node you can send msg.payload straight to a Telegram or email node.
[
{
"id": "cs_inject",
"type": "inject",
"name": "Take screenshot",
"props": [
{
"p": "url",
"v": "https://nodered.org",
"vt": "str"
}
],
"repeat": "",
"once": false,
"topic": "",
"x": 140,
"y": 100,
"wires": [
[
"cs_prep"
]
]
},
{
"id": "cs_prep",
"type": "function",
"name": "Build request",
"func": "// Put your key in an env var (Settings > Environment) named CURLSHOT_KEY\nconst q = new URLSearchParams({\n url: msg.url,\n format: 'png', // png, jpeg, webp or pdf\n full_page: 'true',\n block_cookie_banners: 'true',\n block_ads: 'true'\n});\nmsg.url = 'https://curlshot.com/api/v1/screenshot?' + q.toString();\nmsg.headers = { 'X-Access-Key': env.get('CURLSHOT_KEY') };\nmsg.filename = '/tmp/screenshot-' + Date.now() + '.png';\nreturn msg;",
"outputs": 1,
"x": 330,
"y": 100,
"wires": [
[
"cs_http"
]
]
},
{
"id": "cs_http",
"type": "http request",
"name": "CurlShot API",
"method": "GET",
"ret": "bin",
"paytoqs": "ignore",
"url": "",
"persist": false,
"senderr": false,
"x": 510,
"y": 100,
"wires": [
[
"cs_switch"
]
]
},
{
"id": "cs_switch",
"type": "switch",
"name": "200?",
"property": "statusCode",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "200",
"vt": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 670,
"y": 100,
"wires": [
[
"cs_file"
],
[
"cs_debug"
]
]
},
{
"id": "cs_file",
"type": "file",
"name": "Save file",
"filename": "filename",
"filenameType": "msg",
"appendNewline": false,
"createDir": true,
"overwriteFile": "true",
"encoding": "none",
"x": 830,
"y": 80,
"wires": [
[]
]
},
{
"id": "cs_debug",
"type": "debug",
"name": "Error",
"active": true,
"tosidebar": true,
"complete": "payload",
"targetType": "msg",
"x": 830,
"y": 130,
"wires": []
}
]
Options are documented at Documentation | CurlShot . Feedback welcome, especially on pages that come out wrong.