Element screenshots
An element screenshot returns an image of one DOM node: a pricing table, a chart, a tweet embed, a product card. The output is cropped to that element's box, so you do not need to post-process a full-page image. This guide is part of the capture guides.
The minimal request
The examples read your key from RS_API_KEY. If you do not have a key yet, create a free account first.
With GET, pass selector. The API switches to element mode for you:
curl -G "https://api.renderscreenshot.com/v1/screenshot" \ -H "Authorization: Bearer $RS_API_KEY" \ --data-urlencode "url=https://example.com" \ --data-urlencode "selector=body > div" \ -o element.png
With POST, set the mode and the selector in capture:
curl -X POST https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "capture": { "mode": "element", "selector": "body > div", "selector_required": true } }' \ -o element.png
On example.com, body > div is the centered text block, so the image is that block and not the 1280 x 800 viewport.
Parameters that matter
| Parameter | Default | Notes |
|---|---|---|
capture.mode |
viewport |
Set to element. The GET selector parameter sets it for you. |
capture.selector |
none | Any CSS selector that document.querySelector accepts. The first match is captured. |
capture.selector_required |
false |
Set to true so a missing element fails the request instead of returning another image. |
capture.beyond_viewport |
true |
Includes the parts of the element outside the visible window. |
page.shadow_dom |
false |
Lets selectors reach elements inside open shadow roots. |
viewport.width / viewport.scale |
1280 / 1 |
The element's width depends on the viewport width; scale multiplies the output pixels. |
Set selector_required: true in production. Without it, a redesign that renames a class can quietly change what you capture. With it, you get a target_error (HTTP 422) whose message names the selector, and your error handling can alert you.
Choosing a selector that survives redesigns
Element captures break when the selector breaks. In order of preference:
- A data attribute you add for this purpose:
[data-screenshot="pricing"]. Nobody renames it by accident. - An id:
#pricing-table. - A semantic structure:
main article > figure:first-of-type. - Utility classes such as
.mt-8.grid.gap-4are the most fragile. Build tools and CSS frameworks change them often.
Test the selector on the live page before you put it in code. Open DevTools on the target page and run:
// Paste into the DevTools console of the target page const matches = document.querySelectorAll('[data-screenshot="pricing"]'); console.log(matches.length, matches[0]?.getBoundingClientRect());
matches.length should be 1. If it is 0, the element is created later by JavaScript or sits inside a shadow root or an iframe (see the table below). If it is more than 1, the API captures the first match only.
Element mode when the element loads late
The selector is evaluated after the wait conditions finish. If the element is rendered by JavaScript after load, wait for it first. Using the same selector in both places is the simplest pattern:
{ "url": "https://app.example.com/dashboard", "wait": { "for_selector": "[data-screenshot=\"revenue-chart\"]", "for_selector_mode": "visible", "timeout": 45 }, "capture": { "mode": "element", "selector": "[data-screenshot=\"revenue-chart\"]", "selector_required": true } }
Elements inside a shadow DOM
Web components such as embedded widgets often keep their markup in a shadow root. A normal selector cannot see inside it. Set page.shadow_dom to true so selectors pierce open shadow roots:
{ "url": "https://example.com/widgets", "page": { "shadow_dom": true }, "capture": { "mode": "element", "selector": "product-card .price", "selector_required": true } }
A closed shadow root (attachShadow({ mode: 'closed' })) cannot be reached by any selector. In that case, capture the host element itself (product-card).
SDK examples
All six SDKs use element(selector) to set element mode and the selector together.
Node.js
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') .element('body > div') .scale(2) .format('png') ); await writeFile('element.png', image);
Python
import os from renderscreenshot import Client, TakeOptions client = Client(os.environ['RS_API_KEY']) image = client.take( TakeOptions.url('https://example.com') .element('body > div') .scale(2) .format('png') ) with open('element.png', 'wb') as f: f.write(image)
Ruby
require 'renderscreenshot' client = RenderScreenshot.client(ENV.fetch('RS_API_KEY')) image = client.take( RenderScreenshot::TakeOptions .url('https://example.com') .element('body > div') .scale(2) .format('png') ) File.binwrite('element.png', image)
PHP
use RenderScreenshot\Client; use RenderScreenshot\TakeOptions; $client = new Client(getenv('RS_API_KEY')); $image = $client->take( TakeOptions::url('https://example.com') ->element('body > div') ->scale(2) ->format('png') ); file_put_contents('element.png', $image);
Java
Client client = new Client(System.getenv("RS_API_KEY")); byte[] image = client.take( TakeOptions.url("https://example.com") .element("body > div") .scale(2.0) .format("png") ); Files.write(Path.of("element.png"), image);
Go
client, err := rs.New(os.Getenv("RS_API_KEY")) if err != nil { log.Fatal(err) } image, err := client.Take(ctx, rs.URL("https://example.com"). Element("body > div"). Scale(2). Format(rs.FormatPNG), ) if err != nil { log.Fatal(err) } os.WriteFile("element.png", image, 0o644)
scale(2) doubles the pixel density of the element image, which keeps text sharp when you place the image on a high-density screen. See retina screenshots for the size cost.
Element, clip or full page?
| You know | Use | Behaviour when the layout changes |
|---|---|---|
| A selector for the thing you want | capture.mode: element |
Follows the element |
| Pixel coordinates only (for example from a design spec) | capture.clip |
Stays on the same rectangle, even if the content moves |
| Nothing specific, you want the whole page | capture.mode: full_page |
Captures everything |
The clip region guide compares element mode and clip side by side.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Request fails with a target_error that names your selector |
No element matches at capture time | Check the selector in DevTools on the live page; wait for it with wait.for_selector if JavaScript creates it |
| Image shows a different card than expected | The selector matches several elements; the first one wins | Use an id or a data- attribute, or :nth-of-type() |
| Sticky header or cookie bar overlaps the top of the element | Fixed elements are painted over the element's box | Hide them with page.hide: ["header", ".cookie-bar"], or block.cookie_banners: true |
| Element is empty or shows a skeleton | Captured before its data arrived | Wait for a child that only exists with data |
| Element inside an iframe is never found | Selectors run in the top-level document only | Capture the iframe element itself, or request the iframe's own URL |
| Element inside a web component is never found | It is in a shadow root | Set page.shadow_dom: true; for closed roots, capture the host element |
| Element is narrower or wider than in your browser | Responsive CSS reacts to viewport.width |
Set the viewport width you design for |
| Rounded corners show a white background | The page background is behind the element | Use output.transparent: true with PNG or WebP, see transparent backgrounds |
| Text looks soft on a phone or Retina screen | Output at 1x | Set viewport.scale to 2 |
Limits
- One element per request. To capture several elements of one page, send one request per selector. Each is a separate render and uses a credit.
viewport.widthaccepts 1 to 7,680 px andviewport.scaleaccepts 1 to 3.- The render limit is
wait.timeout, 30 seconds by default and 60 seconds at most.
Related parameters
- Capture parameters:
mode,selector,selector_required,clip,beyond_viewport. - Page modifications:
hide,remove,clickbefore capture. - Wait strategies: make sure the element exists before the capture.