Adding Shiny app’s parameters to the URL

Modify the URL from Shiny UI elements and viceversa
Author

Mauricio “Pachá” Vargas S.

Published

August 10, 2025

Shiny allows to use all of R to visualize information, no matter if it is a sophisticated statistical model or a simple plot. One of the features it does not provide out-of-the-box is adding the selected parameters to the URL, like this:

https://pacha.dev/palmerpenguinsshiny?inputs&species="Adelie"&island="Torgersen"

In order to have this type of URL I shall demonstrate a simple case with golem. Let’s start by creating a project:

golem::create_golem("palmerpenguinsshiny")

Open R/run_app.R and change enableBookmarking = NULL to enableBookmarking = "url".

Open R/app_server.R and add these lines at the end of app_server():

# Bookmarking ----

observe({
    # Trigger this observer every time an input changes
    # strip shiny related URL parameters
    shiny::reactiveValuesToList(input)
    setBookmarkExclude(c(
        "parameter_not_in_url"
    ))
    session$doBookmark()
})

onBookmarked(function(url) {
    updateQueryString(url)
})

Now we need to add contents to the app. Let’s create an app that allows the user to filter by species and island to obtain a plot of the body mass distribution.

Here’s a shortcut to simplify things and use the pipe operator:


usethis::use_pipe()
devtools::document()

Create a copy of the data we need:

penguins_sib <- palmerpenguins::penguins[, c("species", "island", "body_mass_g")]
usethis::use_data(penguins_sib)

Now let’s add contents to app_server(), like this:

#' The application server-side
#'
#' @param input,output,session Internal parameters for {shiny}.
#'     DO NOT REMOVE.
#' @import shiny
#' @import ggplot2
#' @importFrom dplyr filter
#'
#' @noRd
app_server <- function(input, output, session) {
  # Main plot ----

  # Filter by species and island, then show the distribution of body_mass_g
  output$main_plot <- renderPlot({
    req(input$species, input$island)
    penguins_sib %>%
      filter(
        species %in% input$species,
        island %in% input$island
      ) %>%
      ggplot(aes(x = body_mass_g)) +
      geom_histogram(bins = input$bins, fill = input$fill, color = "black") +
      labs(
        title = "Distribution of Body Mass (g)",
        x = "Body Mass (g)",
        y = "Count"
      ) +
      theme_minimal(base_size = 13)
  })

  # Bookmarking ----

  observe({
    # Trigger this observer every time an input changes
    # strip shiny related URL parameters
    shiny::reactiveValuesToList(input)
    setBookmarkExclude(c(
      "fill"
    ))
    session$doBookmark()
  })

  onBookmarked(function(url) {
    updateQueryString(url)
  })
}

Pass the server logic on UI side:

#' The application User-Interface
#'
#' @param request Internal parameter for `{shiny}`.
#'     DO NOT REMOVE.
#' @import shiny
#' @noRd
app_ui <- function(request) {
  tagList(
    # Leave this function for adding external resources
    golem_add_external_resources(),
    # Your application UI logic
    
    # Filters

    sidebarLayout(
      sidebarPanel(
        selectInput("species", "Select Species:", choices = unique(penguins$species), multiple = TRUE),
        selectInput("island", "Select Island:", choices = unique(penguins$island), multiple = TRUE),
        selectInput("fill", "Select Fill Color:", choices = c("#3d809d", "#d04e66", "#365158"),
          multiple = FALSE, selected = "#3d809d"),
        sliderInput("bins", "Number of Bins:", min = 1, max = 50, value = 30)
      ),
      mainPanel(
        plotOutput("main_plot")
      )
    )
  )
}

#' Add external Resources to the Application
#'
#' This function is internally used to add external
#' resources inside the Shiny application.
#'
#' @import shiny
#' @importFrom golem add_resource_path activate_js favicon bundle_resources
#' @noRd
golem_add_external_resources <- function() {
  add_resource_path(
    "www",
    app_sys("app/www")
  )

  tags$head(
    favicon(),
    bundle_resources(
      path = app_sys("app/www"),
      app_title = "palmerpenguins"
    )
    # Add here other external resources
    # for example, you can add shinyalert::useShinyalert()
  )
}

Now run:

devtools::load_all()
run_app()

You should see your app running locally, and when you select the two required fields (species and island) there will be a rendered plot and a URL of the form:

http://127.0.0.1:6736/?_inputs_&species=%22Adelie%22&island=%22Biscoe%22&bins=30

Note that the fill colour is not in the URL but that is intentional :)

The full code is here: https://github.com/pachadotdev/palmerpenguinsshiny.

Don’t forget to add a license to your app (e.g., open dev/01_start.R to select the MIT license, or use Apache with usethis::use_apache_license()).

If this resource was useful to you, please consider donating here: https://buymeacoffee.com/pacha.