Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Yes—you can create JavaScript screenshots of R plots without Shiny. Render the plot in R to a PNG (or another image format), place that file in a small HTML page, and use a browser automation library such as Playwright to capture either the plot element or the whole page. The browser only displays the finished image; it does not need to run R or Shiny.

This separation is usually the simplest and most portable design. If your R process must control the capture, use webshot2 with headless Chrome. If the graphic must be exposed as a browser-facing, network-accessible R device, consider httpgd instead of a static export.

Choose the architecture first

Requirement Recommended route Main trade-off
Capture an ordinary, completed R plot with JavaScript R image output → HTML <img> → Playwright Clear separation between plotting and capture; the image is static.
Run the screenshot from an R script webshot2::webshot() R-side orchestration, but Chrome or a compatible Chromium browser is required.
Provide browser-facing R graphics over a network protocol httpgd More moving parts and deployment decisions than serving a file.
Capture output generated in an R Markdown or knitr document knitr screenshot support with an available browser backend Backend availability and browser settings affect the result.

Shiny is relevant when you need a reactive application, inputs, and server-side sessions. It is not a prerequisite for taking a screenshot of a finished plot.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Method 1: Render in R and capture with Playwright

This method keeps the R and JavaScript responsibilities separate. R creates the pixels; an ordinary HTML page gives the browser a stable element to locate; Playwright controls the browser and writes the screenshot.

1. Render a plot to a file in R

The exact graphics-device arguments vary by R version and device. Verify them against the R installation you deploy. A typical PNG export is:

png("plot.png", width = 1200, height = 800, res = 144)
plot(
  pressure$temperature,
  pressure$pressure,
  type = "b",
  pch = 19,
  col = "#2F6FED",
  xlab = "Temperature",
  ylab = "Pressure",
  main = "Pressure by temperature"
)
dev.off()

For a ggplot2 chart, save it with the package’s file-writing function:

library(ggplot2)

p <- ggplot(mtcars, aes(wt, mpg)) +
  geom_point(color = "#2F6FED", size = 3) +
  theme_minimal(base_size = 14)

ggsave("plot.png", p, width = 8, height = 5, units = "in", dpi = 144)

Choose dimensions and resolution for the final use. A 1200×800 image shown at a much larger CSS size can look soft; an unnecessarily large image increases load and capture time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Put the image in a minimal HTML page

Save this as plot.html in the same directory as plot.png:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>R plot</title>
  <style>
    body { margin: 0; background: #fff; }
    #plot-wrap { width: 1200px; max-width: 100%; }
    #plot { display: block; width: 100%; height: auto; }
  </style>
</head>
<body>
  <main id="plot-wrap">
    <img id="plot" src="plot.png" alt="Pressure by temperature">
  </main>
</body>
</html>

For reliable automation, serve the directory over HTTP rather than depending on a file:// URL. One simple option is:

python -m http.server 8000

Then the page is available at http://127.0.0.1:8000/plot.html.

3. Install Playwright and its browser

npm init -y
npm install playwright
npx playwright install chromium

The browser binary must be available in the environment that runs the script. In CI or a container, install it during image setup and keep the Playwright and browser versions consistent.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Capture the plot element in JavaScript

Playwright’s page and locator screenshot APIs can save an image to a path or return image bytes. This script waits for the image to finish loading, then captures only the plot:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({
    viewport: { width: 1280, height: 900 },
    deviceScaleFactor: 1
  });

  await page.goto('http://127.0.0.1:8000/plot.html', {
    waitUntil: 'networkidle'
  });

  await page.locator('#plot').waitFor({ state: 'visible' });
  await page.locator('#plot').screenshot({
    path: 'r-plot.png',
    type: 'png'
  });

  await browser.close();
})();

Element capture is generally the precise choice for a chart. It avoids page margins, navigation, and unrelated content. To capture the complete scrollable document instead, use:

await page.screenshot({ path: 'full-page.png', fullPage: true, type: 'png' });

To receive bytes for an upload or further processing, omit path:

const imageBytes = await page.locator('#plot').screenshot({ type: 'webp' });

Playwright also supports image format and scale controls. Select PNG for lossless chart text, JPEG when a smaller photographic-style file is acceptable, and WebP when your downstream system supports it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make capture deterministic

Wait for a real ready condition

A fixed sleep can work for a known local file, but it is fragile when the page loads fonts, data, or images asynchronously. Prefer an observable condition:

await page.locator('#plot').waitFor({ state: 'visible' });
await page.waitForFunction(() => {
  const img = document.querySelector('#plot');
  return img && img.complete && img.naturalWidth > 0;
});

For a page that adds a class or text such as data-ready="true", wait for that signal instead. If your plot is generated by JavaScript rather than an image, wait for the chart’s container and any application-specific ready marker.

Control viewport, scale, and clipping

  • Viewport: set an explicit width and height so responsive CSS does not change the layout between runs.
  • Device scale factor: use a known value when pixel dimensions matter.
  • Element versus page: use a locator screenshot for the chart; use fullPage only when surrounding content belongs in the output.
  • CSS size: ensure the image is not being resized unexpectedly by a max-width rule or a flex container.

Pixel-identical output on every computer is not guaranteed. Browser version, operating system, installed fonts, graphics configuration, and rendering settings can change anti-aliasing and text metrics. Pin the browser and run captures in a consistent environment when visual diffs or reproducible artifacts matter.

Method 2: Capture from R with webshot2

webshot2 uses headless Chrome through Chromote and replaces the older PhantomJS-based webshot approach. It requires Chrome or a Chromium-derived browser; documented alternatives include Chromium, Edge, Vivaldi, Brave, and Opera.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install the package in R:

install.packages("webshot2")

For an ordinary URL or static HTML page, use webshot():

library(webshot2)

webshot(
  "http://127.0.0.1:8000/plot.html",
  file = "r-plot-from-r.png",
  vwidth = 1280,
  vheight = 900,
  delay = 0.2
)

Use appshot() for a Shiny application; a non-Shiny URL does not need that path. The package also supports page capture, viewport dimensions, and clipping controls. A delay can cover a known short rendering step, but a page-level ready signal is preferable when you control the HTML.

When httpgd is the better fit

httpgd is a network-accessible R graphics device intended for browser-oriented workflows. It is different from exporting a completed plot to PNG: the graphics device exposes output through network protocols, which can be useful when a browser or another client must interact with a live R graphics stream.

Choose it when the plot must remain connected to an R graphics device or be consumed remotely. Choose a static image plus Playwright when you need a simple, repeatable artifact. The appropriate httpgd integration and function signatures depend on the package version and deployment design, so verify them in the version you install.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Knitr and R Markdown documents

knitr’s screenshot implementation checks for webshot2, Chrome/Chromium, and Chromote, then forwards settings such as viewport width, height, and delay. This support can cover widgets, embedded URLs, and Shiny-related outputs, but it does not make Shiny a requirement for an ordinary static HTML screenshot. Confirm which backend is available in the execution environment before relying on a document build.

Serve, secure, and scale the workflow

Local and deployed URLs

A local HTTP server is adequate for development. In production, host the HTML and image behind the access controls your application requires. Keep the URL stable for the duration of the capture and ensure the browser process can resolve it from its network namespace, especially in containers.

Fonts and visual assets

Install the same fonts in the capture environment as in development, or use web fonts that are fully loaded before capture. Missing fonts are a common cause of changed line breaks and chart labels. Wait for font readiness when needed:

await page.evaluate(() => document.fonts.ready);

Parallel jobs and resource use

Launching a new browser for every plot adds overhead. For batches, reuse one browser process and create isolated pages or contexts. Limit concurrency to the CPU and memory available; a full-page capture of a very large document can consume substantially more memory than an element screenshot. Cache the rendered R image when the underlying data has not changed.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

“Target page, context or browser has been closed”

The browser process may have crashed, been killed for exceeding container memory, or failed to launch. Install the required Chromium binary, check sandbox restrictions in your runtime, and reduce parallel captures or page size.

The screenshot is blank

Usually the image URL is wrong, the server is unreachable from the browser, or capture happens before rendering. Inspect page.url(), check the image response, wait for naturalWidth > 0, and capture a diagnostic full page.

The plot is cut off

You may be capturing the viewport rather than the element or full document. Use a locator screenshot for the complete image, or fullPage: true for all scrollable content. Check CSS overflow and explicit dimensions.

Fonts or labels differ between runs

Use the same OS, browser build, device scale factor, and installed fonts. Wait for document.fonts.ready and avoid relying on system-default font fallbacks.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

webshot2 cannot find Chrome

Install Chrome or a compatible Chromium browser and make it visible to the process running R. Confirm the package’s browser-discovery behavior on your platform and use a supported browser executable.

The image has unexpected margins

Element screenshots include the element’s rendered box. Remove default body margins, inspect padding and borders, and capture the inner image rather than its outer wrapper when necessary.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. It can fetch a URL and return PNG, JPEG, WebP, or PDF without you installing Playwright or managing a browser process. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

For a page that already contains your rendered R image, call the API directly (see the ScreenshotNeo documentation):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = require('node:fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also supports full-page and CSS-selector captures, dark mode, device presets and custom viewports, retina scale, custom CSS and JavaScript, click and wait actions, blocked resources, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, selectable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month with no card. Starter is $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free, and every feature is on every plan. Create a free ScreenshotNeo account to start.

Practical decision checklist

  • Use R’s graphics device when the source of truth is a completed static plot.
  • Use Playwright when JavaScript should own browser navigation and capture.
  • Use webshot2 when an R script or document build should own the browser call.
  • Use httpgd when a live, network-accessible R graphics device is part of the design.
  • Set viewport, scale, fonts, and a readiness condition explicitly for repeatable output.
  • Capture a locator rather than the whole page when only the plot belongs in the image.

Frequently Asked Questions

Does the R process need to stay running while Playwright captures a PNG?

No. Once R has written the image and the HTML page can load it, Playwright only needs the page and its assets. R must remain involved only when the page generates graphics dynamically or you use a live device such as httpgd.

Can I return the screenshot without writing a file?

Yes. Playwright’s screenshot methods return a buffer when you omit the path, allowing you to upload or process the bytes directly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which format is best for plot text?

PNG is the safest lossless default for labels, lines, and axes. WebP can reduce size when supported by your destination; JPEG is generally less suitable for sharp chart text.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.