How to Screenshot a Single Element with dom-to-image
Capture one DOM element as PNG, JPEG, SVG, Blob, canvas, or pixels with dom-to-image, including filters, fonts, cross-origin fixes, and troubleshooting.

To screenshot one element with dom-to-image, resolve the element to a DOM node and pass that node to domtoimage.toPng(node). The promise resolves to a PNG data URL that you can display, download, upload, or convert to a Blob. The same node can be rendered as JPEG, SVG, Blob, canvas, or raw pixel data.
import domtoimage from 'dom-to-image';
const node = document.getElementById('my-element');
if (!node) {
throw new Error('Element not found');
}
domtoimage.toPng(node)
.then((dataUrl) => {
const image = new Image();
image.src = dataUrl;
document.body.appendChild(image);
})
.catch((error) => {
console.error('Could not render element', error);
});
This is a DOM renderer, not an operating-system screenshot. It clones the selected subtree, copies computed styles, recreates pseudo-elements, embeds fonts and images, serializes the result through SVG foreignObject, and uses an image and canvas path for raster output. That design makes the target element easy to isolate, but external resources, canvas security, browser behavior, and content size affect the result. See the original dom-to-image documentation for the package API and implementation notes.
1. Install dom-to-image and create a target element
Install the package in a browser-based JavaScript project:

npm install dom-to-image
Give the element a stable ID or class. The element must exist in the browser DOM when you call the renderer.
<article id="invoice-card" class="invoice-card">
<h1>Invoice #1042</h1>
<p>Paid · March 2026</p>
<strong>$249.00</strong>
</article>
<button id="save-invoice">Save image</button>
Then import the package and resolve the node:
import domtoimage from 'dom-to-image';
const card = document.querySelector('.invoice-card');
if (!card) {
throw new Error('The invoice card is not in the DOM');
}
const dataUrl = await domtoimage.toPng(card);
console.log(dataUrl.slice(0, 40));
Pass the actual node, not a selector string. document.querySelector, getElementById, or a framework ref can supply the node. The null check is ordinary defensive DOM code; it is not a special dom-to-image requirement.
2. Display, download, or upload the result
Display the image
const preview = document.createElement('img');
preview.alt = 'Rendered invoice';
preview.src = await domtoimage.toPng(card);
document.body.appendChild(preview);
Download a PNG
const dataUrl = await domtoimage.toPng(card);
const link = document.createElement('a');
link.download = 'invoice-1042.png';
link.href = dataUrl;
link.click();
Use a Blob for uploads
const blob = await domtoimage.toBlob(card);
const form = new FormData();
form.append('file', blob, 'invoice-1042.png');
await fetch('/uploads', { method: 'POST', body: form });
A data URL is convenient for a preview or small download. A Blob is generally a better boundary for network uploads because it avoids keeping a long base64 string in application state.
3. Choose the output format
All top-level functions accept a DOM node and rendering options and return promises. Pick the output for the next operation:
| Method | Result | Use it when |
|---|---|---|
toPng |
PNG data URL | You need lossless raster output or a quick browser preview. |
toJpeg |
JPEG data URL | You want a smaller compressed image and can accept lossy encoding. |
toSvg |
SVG data URL | You want the serialized SVG container. |
toBlob |
Blob | You will upload, store, or download the result. |
toCanvas |
HTML canvas | You need to draw, resize, or process it with browser canvas APIs. |
toPixelData |
Raw RGBA pixel data | You need image analysis or custom pixel processing. |
const jpegUrl = await domtoimage.toJpeg(card, { quality: 0.85 });
const svgUrl = await domtoimage.toSvg(card);
const canvas = await domtoimage.toCanvas(card);
const pixels = await domtoimage.toPixelData(card);
The quality option applies to JPEG output and ranges from 0 to 1. PNG does not use JPEG quality settings.
4. Control what gets rendered
Exclude descendants with filter
Use filter(node) to omit descendants. Return true to keep a node and false to exclude it. Excluding a parent excludes its entire subtree. The filter is not called for the root capture node, so it cannot remove the root itself.
const options = {
filter(node) {
return node.tagName !== 'BUTTON';
}
};
const png = await domtoimage.toPng(card, options);
This is useful for removing print controls, menus, live status badges, or other descendants that should not appear in the exported image.
Set a background color
const png = await domtoimage.toPng(card, {
bgcolor: '#ffffff'
});
A background is helpful when the source element is transparent but the destination expects an opaque image.
Override dimensions
const png = await domtoimage.toPng(card, {
width: 1200,
height: 800
});
Width and height change the rendered node dimensions. They do not automatically reproduce a responsive layout at every breakpoint, so inspect the output at the dimensions you intend to publish.
Apply temporary styles
const png = await domtoimage.toPng(card, {
style: {
padding: '32px',
borderRadius: '0',
boxShadow: 'none'
}
});
Style overrides are applied to the cloned root for the render. Keep the source page unchanged and put export-only presentation rules here.
Handle cache and failed images
const png = await domtoimage.toPng(card, {
cacheBust: true,
imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
});
cacheBust appends the current time to resource URLs. imagePlaceholder supplies a data URL when an image fetch fails. Without a placeholder, an image failure can reject the render.
5. Wait for fonts, images, and layout before capture
Call dom-to-image only after the element has its final layout. A practical sequence is:
- Render the component and make it visible.
- Wait for document fonts when the browser exposes
document.fonts.ready. - Wait for images inside the target to finish loading.
- Capture on the next animation frame so layout and paint have settled.
async function waitForImages(root) {
const images = [...root.querySelectorAll('img')];
await Promise.all(images.map((img) => {
if (img.complete) return Promise.resolve();
return new Promise((resolve) => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
});
}));
}
async function captureAfterLoad(root) {
if (document.fonts?.ready) {
await document.fonts.ready;
}
await waitForImages(root);
await new Promise(requestAnimationFrame);
return domtoimage.toPng(root);
}
const png = await captureAfterLoad(card);
This does not bypass cross-origin restrictions. It only prevents an avoidable race where the clone is made before a font or image has loaded.
6. Resource, browser, and content limitations
Because the library clones and fetches resources, remote images, CSS background images, and web fonts can change the result. A canvas that contains cross-origin content can become tainted, preventing later pixel reads or causing a render failure. Make assets same-origin where possible, configure appropriate CORS response headers, or replace problematic assets with data URLs before capture.
The original README contains historical warnings about Internet Explorer lacking SVG foreignObject and Safari enforcing stricter security around it. Those statements describe the project’s documentation at the time and are not a current browser-support test. Validate the exact browsers and content your application promises.
The separate dom-to-image-more fork documents additional caveats: rendering requires a browser DOM rather than server-only execution, cross-origin iframe content cannot be accessed, browsers limit canvas dimensions, and video needs a poster or a caller-created image or canvas representation. Treat those as fork-specific documentation when deciding whether they apply to your package.
7. Common errors and fixes
| Error or symptom | Likely cause | Fix |
|---|---|---|
Cannot read properties of null |
The selector ran before the element existed or matched nothing. | Run after rendering, use a stable selector, and check the node before calling the API. |
| Blank or missing images | An image was still loading, failed to fetch, or was blocked by origin policy. | Wait for images, use cacheBust, fix CORS, or provide imagePlaceholder. |
| Custom font is missing | The font had not loaded or could not be fetched by the cloned document. | Await document.fonts.ready and verify the font response is accessible. |
| Render rejects with a security or tainted-canvas error | Cross-origin image, background, font, iframe, or canvas content. | Use same-origin assets, correct CORS headers, or remove/replace the resource. |
| Buttons or controls appear in the export | They are descendants of the target and were cloned normally. | Exclude them with filter or hide them with an export style. |
| Text is clipped | The target has fixed dimensions, overflow rules, or a render size different from the source. | Inspect computed dimensions, set explicit width/height, and adjust overflow. |
| Very large capture fails | Canvas or browser bitmap dimensions are limited. | Capture smaller sections, reduce dimensions, or use a server-side capture service. |
| Video is blank | Video frames are not represented as ordinary static image content. | Use a poster frame or draw a frame to a canvas before capture. |
8. Performance and reliability guidance
- Capture only the required subtree. Cloning a small card is faster and uses less memory than cloning the entire page.
- Reuse a prepared export component when you need many images instead of repeatedly changing a complex live layout.
- Wait for fonts and images once, then capture multiple formats from the settled node.
- Prefer
toBlobfor uploads and avoid storing large base64 strings in application state. - Set explicit dimensions for repeatable output, especially when fonts, responsive CSS, or animation are involved.
- Disable animations and blinking cursors in export styles so two captures do not differ.
- Test the largest expected element. Browser canvas limits are practical constraints, not just performance concerns.
dom-to-image runs in the browser and consumes the user’s CPU and memory. For unattended batches, pages you do not control, or content that requires cookie handling and browser automation, a screenshot API can remove that setup.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, with options for full-page capture, a CSS element selector, dark mode, device presets, retina scale, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, timezone, geolocation, caching, signed links, asynchronous jobs, bulk capture, and usage reporting. The API also accepts parameter names used by other screenshot services, which simplifies migration. See the ScreenshotNeo API documentation for the complete option list.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
For a single element, supply the API’s element selector option alongside the target URL. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account and start with the included 1,000 screenshots.
10. FAQ
Does dom-to-image capture the whole page?
It captures the node you pass. Pass a page container for a larger region, but the library does not automatically provide a browser-style full-page scroll capture.
Can I pass a CSS selector directly?
No. Resolve the selector first with querySelector or another DOM method, then pass the resulting element node.
Which format should I use for transparent graphics?
Use PNG or SVG and avoid setting an opaque bgcolor. JPEG does not preserve transparency.
Why is the output different from the visible page?
The renderer clones computed styles and resources. Fonts, external assets, animations, cross-origin content, and dimensions can therefore produce differences. Capture after the layout is settled and make export dimensions explicit.
Is dom-to-image suitable for server-side rendering?
The original package requires a browser DOM. For server-side or scheduled captures, use a browser service such as ScreenshotNeo or run a real browser environment that loads the page.


