Here's a working shiny app to allow arbitrary QR generate (of text), showing in both SVG and PNG.
library(shiny)
ui <- fluidPage(sidebarLayout(
sidebarPanel(
textInput("text", label = NULL, placeholder = "(some phrase)"),
imageOutput("svgplot")
),
mainPanel(
plotOutput("plot")
)
))
server <- function(input, output, session) {
QR <- eventReactive(input$text, {
qrcode::qr_code(input$text)
})
output$svgplot <- renderImage({
txt <- isolate(input$text)
tf <- tempfile(fileext = ".svg")
qrcode::generate_svg(QR(), tf, size = 100, foreground = "black", background = "white", show = FALSE)
list(
src = normalizePath(tf),
contentType = "image/svg+xml",
width = 100, height = 100,
alt = paste("My QR code:", sQuote(txt, FALSE))
)
}, deleteFile = TRUE)
output$plot <- renderPlot({ plot(QR()) })
}
shinyApp(ui, server)

You don't need to show both, I thought I'd compare/contrast how to show them since they require different steps.
Key takeaways:
renderImage
allows us to show an arbitrary image that the browser should support. The expression in this call must be a list
with the attributes for the HTML img
attribute, so here my list(...)
is creating
<img src="path/to/tempfile.svg" contentType="image/svg+xml" width=100 height=100 alt="...">
I'm doing a two-step here: create the QR object as reactive data, and then anything that will use that will depend on QR()
(my QR object). This way there will be fewer reactive-chain calls. This may not be strictly necessary if all you're doing is showing a single QR code, over to you.
shiny::renderImage
requires the deleteFile=
argument; if all you want to is show it, this is fine; if the user wants to right-click on the displayed SVG file and download it locally, it's still fine. In fact, since the "link" text is data:image/svg+xml;base64,PD94bWwgdmVy...
and is a fairly long string (39K chars in one example), even if the temp file is deleted, this link remains unchanged and working.