Scoping rules for Shiny apps

Where you define objects will determine where the objects are visible. There are three different levels of visibility that you’ll want to be aware of when writing Shiny apps.
Published

December 1, 2017

Scoping

Where you define objects will determine where the objects are visible. There are three different levels of visibility that you’ll want to be aware of when writing Shiny apps. Some objects are visible within the server code of each user session; other objects are visible in the server code across all sessions (multiple users could use a shared variable); and yet others are visible in the server and the ui code across all user sessions.

This document describes how scoping works within a single R process. One R process can support multiple Shiny sessions. Some hosting platforms (including Posit Connect, Shiny Server Pro, and shinyapps.io) also allow running multiple R processes to handle heavier traffic. Within each R process, the scoping works as explained below, but between the R processes, no objects are shared. So, for example, if you configure Posit Connect to start a new R process for each connection to your app, no objects will ever be shared between different sessions of the app, since these sessions all belong to different R processes. (To learn more about about this, and how it affects the performance of your apps, see this article.)

Per-session objects

In app.R, the server function takes three arguments: input, output and session.

server <- function(input, output, session) {
  # Server code here
  # ...
})

The function is called once for each session. In other words, the server function is called each time a web browser is pointed to the Shiny application.

Everything within this function is instantiated separately for each session. This includes the input, output and session objects that are passed to it: each session has its own input, output and session objects, visible within this function.

Other objects inside the function, such as variables and functions, are also instantiated for each session. In this example, each session will have its own variable named startTime, which records the start time for the session:

server <- function(input, output, session) {
  startTime <- Sys.time()

  # ...
}

Objects visible across all sessions

You might want some objects to be visible across all sessions. For example, if you have large data structures, or if you have utility functions that are not reactive (ones that don’t involve the input or output objects), then you can create these objects once and share them across all user sessions (within the same R process), by placing them in app.R, but outside of the server function definition.

For example:

# A read-only data set that will load once, when Shiny starts, and will be
# available to each user session
bigDataSet <- read.csv("bigdata.csv")

# A non-reactive function that will be available to each user session
utilityFunction <- function(x) {
  # Function code here
  # ...
}

server <- function(input, output, session) {
  # Server code here
  # ...
}

You could put bigDataSet and utilityFunction inside the server function, but doing so will be less efficient, because they will be created each time a user connects.

If the objects change, then the changed objects will be visible in every user session. But note that you would need to use the <<- assignment operator to change bigDataSet, because the <- operator only assigns values in the local environment.

varA <- 1
varB <- 1
listA <- list(X = 1, Y = 2)
listB <- list(X = 1, Y = 2)

server <- function(input, output, session) {
  # Create a local variable varA, which will be a copy of the shared variable
  # varA plus 1. This local copy of varA is not be visible in other sessions.
  varA <- varA + 1

  # Modify the shared variable varB. It will be visible in other sessions.
  varB <<- varB + 1

  # Makes a local copy of listA
  listA$X <- 5

  # Modify the shared copy of listB
  listB$X <<- 5

  # ...
}

Things work this way because app.R is sourced when you start your Shiny app. Everything in this script is run immediately. However, your server function is only actually called when a web browser connects and a new session is started

Global objects

Objects defined in global.R are similar to those defined in app.R outside of the server function definition, with one important difference: they are loaded into the global environment of the R session; all R code in a Shiny app is run in the global environment or a child of it.

In practice, there aren’t many times where it’s necessary to share variables between server and ui. The code in ui is run once, when the Shiny app is started and it generates an HTML file which is cached and sent to each web browser that connects. This may be useful for setting some shared configuration options.

Scope for included R files

If you want to split the server or ui code into multiple files, you can use source(local = TRUE) to load each file. You can think of this as putting the code in-line, so the code from the sourced files will receive the same scope as if you copied and pasted the text right there.

This example app.R file shows how sourced files will be scoped:

# Objects in this file are shared across all sessions in the same R process
source('all_sessions.R', local = TRUE)

server <- function(input, output, session) {
  # Objects in this file are defined in each session
  source('each_session.R', local = TRUE)

  output$text <- renderText({
    # Objects in this file are defined each time this function is called
    source('each_call.R', local = TRUE)

    # ...
  })
}

If you use the default value of local = FALSE, then the file will be sourced in the global environment.