How to Take Screenshots and Generate PDFs in R
webshot2 requires a local Chromium install and breaks on RStudio Connect and Posit Cloud. Here's a zero-dependency alternative: one httr2 call, binary response. Works anywhere R runs — including Shiny apps, R Markdown reports, and Plumber APIs.
The standard R approach for capturing web content is webshot2, which wraps a local Chromium installation via chromote. It works locally but breaks on RStudio Connect, Posit Cloud, and any environment where you can't install or run a headless browser.
Here's the simpler path: one httr2 call, binary response. Works anywhere R runs.
Setup
No package installs beyond what most R projects already have:
# install.packages("httr2") # if not already installed
library(httr2)
Screenshot from a URL
pagebolt_screenshot <- function(url, full_page = TRUE, block_banners = TRUE) {
api_key <- Sys.getenv("PAGEBOLT_API_KEY", unset = stop("PAGEBOLT_API_KEY not set"))
resp <- request("https://pagebolt.dev/api/v1/screenshot") |>
req_headers("x-api-key" = api_key) |>
req_body_json(list(
url = url,
fullPage = full_page,
blockBanners = block_banners
)) |>
req_perform()
resp_body_raw(resp)
}
# Save to file
image_bytes <- pagebolt_screenshot("https://example.com")
writeBin(image_bytes, "screenshot.png")
cat("Screenshot saved:", length(image_bytes), "bytes\n")
PDF from a URL
pagebolt_pdf_url <- function(url, block_banners = TRUE) {
api_key <- Sys.getenv("PAGEBOLT_API_KEY", unset = stop("PAGEBOLT_API_KEY not set"))
resp <- request("https://pagebolt.dev/api/v1/pdf") |>
req_headers("x-api-key" = api_key) |>
req_body_json(list(url = url, blockBanners = block_banners)) |>
req_perform()
resp_body_raw(resp)
}
pdf_bytes <- pagebolt_pdf_url("https://example.com")
writeBin(pdf_bytes, "report.pdf")
PDF from HTML (R Markdown / knitr output)
This is the key use case for R: render a report to HTML first, then capture it as a pixel-perfect PDF:
pagebolt_pdf_html <- function(html) {
api_key <- Sys.getenv("PAGEBOLT_API_KEY", unset = stop("PAGEBOLT_API_KEY not set"))
resp <- request("https://pagebolt.dev/api/v1/pdf") |>
req_headers("x-api-key" = api_key) |>
req_body_json(list(html = html)) |>
req_perform()
resp_body_raw(resp)
}
# Render an R Markdown document to HTML string, then capture as PDF
rmd_to_pdf <- function(rmd_file, output_file = "output.pdf") {
# Render to a temp HTML file
tmp_html <- tempfile(fileext = ".html")
rmarkdown::render(rmd_file, output_format = "html_document", output_file = tmp_html, quiet = TRUE)
html <- paste(readLines(tmp_html), collapse = "\n")
pdf_bytes <- pagebolt_pdf_html(html)
writeBin(pdf_bytes, output_file)
invisible(output_file)
}
rmd_to_pdf("analysis.Rmd", "analysis.pdf")
Shiny app — export dashboard as PDF
library(shiny)
ui <- fluidPage(
titlePanel("Sales Dashboard"),
plotOutput("sales_plot"),
downloadButton("download_pdf", "Export as PDF")
)
server <- function(input, output, session) {
output$sales_plot <- renderPlot({
plot(1:10, main = "Sales Data")
})
output$download_pdf <- downloadHandler(
filename = function() paste0("dashboard-", Sys.Date(), ".pdf"),
content = function(file) {
# Capture the live Shiny app URL
app_url <- paste0(session$clientData$url_protocol, "//",
session$clientData$url_hostname, ":",
session$clientData$url_port,
session$clientData$url_pathname)
pdf_bytes <- pagebolt_pdf_url(app_url)
writeBin(pdf_bytes, file)
},
type = "application/pdf"
)
}
shinyApp(ui, server)
Plumber API — screenshot endpoint
library(plumber)
#* Capture a screenshot
#* @param url The URL to capture
#* @serializer contentType list(type="image/png")
#* @post /screenshot
function(req) {
body <- jsonlite::fromJSON(req$postBody)
pagebolt_screenshot(body$url)
}
#* Generate a PDF from HTML
#* @serializer contentType list(type="application/pdf")
#* @post /pdf
function(req) {
body <- jsonlite::fromJSON(req$postBody)
pagebolt_pdf_html(body$html)
}
Reusable helper script
Drop this into R/pagebolt.R in any project:
#' PageBolt API client
#'
#' Requires PAGEBOLT_API_KEY environment variable.
#' @import httr2
.pagebolt_post <- function(endpoint, body) {
api_key <- Sys.getenv("PAGEBOLT_API_KEY", unset = stop("Set PAGEBOLT_API_KEY"))
resp <- request(paste0("https://pagebolt.dev/api/v1", endpoint)) |>
req_headers("x-api-key" = api_key) |>
req_body_json(body) |>
req_error(body = \(r) resp_body_string(r)) |>
req_perform()
resp_body_raw(resp)
}
pagebolt_screenshot <- function(url, ...) .pagebolt_post("/screenshot", list(url = url, ...))
pagebolt_pdf_url <- function(url, ...) .pagebolt_post("/pdf", list(url = url, ...))
pagebolt_pdf_html <- function(html, ...) .pagebolt_post("/pdf", list(html = html, ...))
No webshot2, no chromote, no Chromium binary. httr2 is a base R tidyverse package — it's already installed in virtually every R environment including RStudio Connect and Posit Cloud.
Get Started Free
100 requests/month, no credit card
Screenshots, PDFs, and video recordings from your R app — no webshot2, no Chromium, works on RStudio Connect, Posit Cloud, and Shiny deployments.
Get Your Free API Key →