Example: Spotify login to display listening data
Source:vignettes/example-spotify.Rmd
example-spotify.RmdOverview
This example lets a user connect their Spotify account and displays their top tracks. It uses the same provider, client, and module setup as Usage, then calls Spotify’s API with the access token. Spotify supplies profile data through its API rather than an OIDC ID token.
The helper identifies accounts using immutable
account_id, as specified in Spotify’s
May 2026 changes. Before upgrading an existing app, migrate stored
id mappings and audit digests using both identifiers from
an authenticated profile. Do not merge accounts by email or display
name. allow_legacy_id = TRUE permits a temporary fallback
only when account_id is missing; it does not preserve old
mappings when both exist.
Spotify app registration
- Create an app in the Spotify developer dashboard.
- Register
http://127.0.0.1:8100as its redirect URI. Spotify accepts a loopback IP address for local HTTP development, but notlocalhost. See Spotify’s redirect URI rules. - Store the credentials as
SPOTIFY_OAUTH_CLIENT_IDandSPOTIFY_OAUTH_CLIENT_SECRETin your R environment. See the environment setup example for.Renvironsyntax. - Check your app’s development-mode access rules. Development apps require the owner to have Spotify Premium, and test users need to be on the app’s allowlist.
Shiny app example
Save this as app.R, run it, and open
http://127.0.0.1:8100 in a regular browser. After
connecting Spotify, click Load top tracks.
library(shiny)
library(shinyOAuth)
client <- oauth_client(
provider = oauth_provider_spotify(),
client_id = Sys.getenv("SPOTIFY_OAUTH_CLIENT_ID"),
client_secret = Sys.getenv("SPOTIFY_OAUTH_CLIENT_SECRET"),
redirect_uri = "http://127.0.0.1:8100",
scopes = c("user-read-private", "user-top-read")
)
ui <- oauth_ui(fluidPage(
h2("My top tracks"),
actionButton("load", "Load top tracks"),
tableOutput("tracks")
), id = "auth", client = client)
server <- function(input, output, session) {
auth <- oauth_module_server("auth", client)
tracks <- eventReactive(input[["load"]], {
req(auth[["authenticated"]])
tryCatch({
response <- perform_resource_req(
auth[["token"]],
"https://api.spotify.com/v1/me/top/tracks",
query = list(limit = 10, time_range = "short_term")
)
httr2::resp_check_status(response)
body <- httr2::resp_body_json(response, simplifyVector = FALSE)
vapply(body[["items"]], function(track) track[["name"]], character(1))
}, error = function(e) NULL)
})
output[["tracks"]] <- renderTable({
req(auth[["authenticated"]])
values <- tracks()
validate(need(!is.null(values), "Could not load tracks. Try again later."))
validate(need(length(values) > 0, "No listening data to show yet."))
data.frame(Track = values)
})
}
runApp(shinyApp(ui, server), port = 8100, launch.browser = FALSE)API requests and scopes
user-top-read grants permission to read listening
preferences. The request above asks for up to ten tracks from the
short-term range. The module obtains and stores the access token;
perform_resource_req() uses it to call the API.
eventReactive() loads the data when the button is clicked,
and renderTable() displays the result.
For other data, choose the API URL and requested scopes together. A successful login does not mean every API is available to the app. Empty results can be normal for accounts without listening history; API errors can indicate missing permissions, app access restrictions, or request limits.
Full dashboard example
A larger example is installed with the package. It adds profile information, currently playing music, top artists, and recent tracks, with a bslib layout. It uses the same authentication setup as the example above.
install.packages(c("bslib", "ggplot2", "DT", "purrr", "dplyr"))
dashboard_file <- system.file(
"examples", "spotify-dashboard.R", package = "shinyOAuth", mustWork = TRUE
)
file.show(dashboard_file) # Read or copy the complete source.
source(dashboard_file) # Run after setting your Spotify credentials.The source groups the code into client setup, API helpers, UI, and server logic. It validates Spotify links and image URLs before rendering them and keeps table escaping enabled. Preserve those protections when adapting it; profile and track text can contain characters that would otherwise become HTML. Available data still depends on Spotify’s API access rules for your app.