Code
# Automatically bookmark every time an input changes
observe({
reactiveValuesToList(input)
session$doBookmark()
})
# Update the query string
onBookmarked(updateQueryString)
Chapter section list
By default, Shiny does not expose the current state of the app in its URL. To share the result of a special state you need some extra work and create bookmarks.
Let’s take a simple app that we want to make bookmarkable. This app draws Lissajous figures, which replicate the motion of a pendulum. This app can produce a variety of interesting patterns that you might want to share.
R Code 11.1 : Lissajous’sche figures
Procedure 11.1 : Creating bookmarks of Shiny states
There are three things to make Shiny apps bookmarkable:
bookmarkButton()
to the UI. This generates a button that the user clicks to generate the bookmarkable URL.value
for each input control. This means there’s no longer a single static UI but multiple possible UIs that depend on parameters in the URL; i.e. it has to be a function.enableBookmarking = "url"
to the shinyApp()
call.R Code 11.2 : Lissajous’sche figures
“Generating a bookmark” means recording the current values of the inputs in the parameters of URL.
For example:
Instead of providing an explicit button, another option is to automatically update the URL in the browser. This allows your users to use the user bookmark command in their browser, or copy and paste the URL from the location bar.
Automatically updating the URL requires a little boilerplate in the server function:
# Automatically bookmark every time an input changes
observe({
reactiveValuesToList(input)
session$doBookmark()
})
# Update the query string
onBookmarked(updateQueryString)
observe({ reactiveValuesToList(input) session$doBookmark() }) # Update the query string onBookmarked(updateQueryString)
R Code 12.1 : Lissajous’sche figures with URL updating automatially
So far we’ve used enableBookmarking = "url"
which stores the state directly in the URL. This a good place to start because it’s very simple and works everywhere you might deploy your Shiny app. As you can imagine, however, the URL is going to get very long if you have a large number of inputs, and it’s obviously not going to be able to capture an uploaded file.
For these cases, you might instead want to use enableBookmarking = "server"
, which saves the state to an .rds
file on the server. This always generates a short, opaque, URL but requires additional storage on the server.
shinyApp(ui, server, enableBookmarking = "server")
shinyapps.io
doesn’t currently support server side bookmarking, so you’ll need to try this out locally. If you do so, you’ll see that the bookmark button generates URLs like:
http://127.0.0.1:4087/?state_id=0d645f1b28f05c97
Which are paired with matching directories in your working directory: shiny_bookmarks/0d645f1b28f05c97
The main drawbacks with server bookmarking is that it requires files to be saved on the server, and it’s not obvious how long these need to hang around for. If you’re bookmarking complex state and you never delete these files, your app is going to take up more and more disk space over time. If you do delete the files, some old bookmarks are going to stop working.
Automated bookmarking relies on the reactive graph. It seeds the inputs with the saved values then replays all reactive expressions and outputs, which will yield the same app that you see, as long as your app’s reactive graph is straightforward. This section briefly covers some of the cases which need a little extra care:
repeatable()
; see the documentation for more details.tabsetPanel()
.setBookmarkExclude()
somewhere in your server function. For example, setBookmarkExclude(c("secret1", "secret2"))
will ensure that the secret1
and secret2
inputs are not bookmarked.reactiveValues()
object (as we’ll discuss in Chapter Chapter 16), you’ll need to use the onBookmark()
and onRestore()
callbacks to manually save and load your additional state. See Advanced Bookmarking for more details.Generate app for visualizing the results of ambient::noise_simplex()
. Your app should allow the user to control the frequency, fractal, lacunarity, and gain, and be bookmarkable. How can you ensure the image looks exactly the same when reloaded from the bookmark? (Think about what the seed argument implies).
Make a simple app that lets you upload a csv file and then bookmark it. Upload a few files and then look in shiny_bookmarks. How do the files correspond to the bookmarks? (Hint: you can use readRDS() to look inside the cache files that Shiny is generating).
#> data frame with 0 columns and 0 rows
Session Info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.5.1 (2025-06-13)
#> os macOS Sequoia 15.5
#> system aarch64, darwin20
#> ui X11
#> language (EN)
#> collate en_US.UTF-8
#> ctype en_US.UTF-8
#> tz Europe/Vienna
#> date 2025-07-17
#> pandoc 3.7.0.2 @ /opt/homebrew/bin/ (via rmarkdown)
#> quarto 1.8.4 @ /usr/local/bin/quarto
#>
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> package * version date (UTC) lib source
#> cli 3.6.5 2025-04-23 [1] CRAN (R 4.5.0)
#> curl 6.4.0 2025-06-22 [1] CRAN (R 4.5.0)
#> digest 0.6.37 2024-08-19 [1] CRAN (R 4.5.0)
#> evaluate 1.0.4 2025-06-18 [1] CRAN (R 4.5.0)
#> fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.5.0)
#> glossary * 1.0.0.9003 2025-06-08 [1] local
#> htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.5.0)
#> htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.5.0)
#> jsonlite 2.0.0 2025-03-27 [1] CRAN (R 4.5.0)
#> knitr 1.50 2025-03-16 [1] CRAN (R 4.5.0)
#> rlang 1.1.6 2025-04-11 [1] CRAN (R 4.5.0)
#> rmarkdown 2.29 2024-11-04 [1] CRAN (R 4.5.0)
#> rstudioapi 0.17.1 2024-10-22 [1] CRAN (R 4.5.0)
#> rversions 2.1.2 2022-08-31 [1] CRAN (R 4.5.0)
#> sessioninfo 1.2.3 2025-02-05 [1] CRAN (R 4.5.0)
#> xfun 0.52 2025-04-02 [1] CRAN (R 4.5.0)
#> xml2 1.3.8 2025-03-14 [1] CRAN (R 4.5.0)
#> yaml 2.3.10 2024-07-26 [1] CRAN (R 4.5.0)
#>
#> [1] /Library/Frameworks/R.framework/Versions/4.5-arm64/library
#> [2] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
#> * ── Packages attached to the search path.
#>
#> ──────────────────────────────────────────────────────────────────────────────