Deploy an LLM-powered Shiny for R app to Connect Cloud
You have a Shiny app written in R that calls the OpenAI API.
Your API key works like a password: anyone who has it can use your OpenAI account. So keep it out of your code and your GitHub repository.
Instead, you enter the key in Connect Cloud. Connect Cloud stores it encrypted and passes it to your app when the app runs. The app reads it by name from the OPENAI_API_KEY environment variable. This guide shows how to enter the key for each way of deploying.
The sample app generates a fake dataset from your description, summarizes it, and lets you download it as a CSV file.

The source code is available in the examples-shiny-r-llm repository.
Getting started
Whichever method you choose, you need the following:
- A Connect Cloud account.
- An OpenAI API key. See the OpenAI quickstart.
- A Shiny for R app that reads the key from the
OPENAI_API_KEYenvironment variable. This guide uses the sample app, which has a main file (Connect Cloud calls it the primary file) namedapp.R. - If you publish from GitHub, a
renv.lockormanifest.jsonfile that lists the R packages your content uses. Posit Publisher and push-button publishing do this for you.
Save this file as app.R. It reads your key with Sys.getenv("OPENAI_API_KEY").
app.R
library(shiny)
library(httr)
library(jsonlite)
library(DT)
library(stringr)
library(tidyverse)
library(bslib)
library(janitor)
library(shinyjs)
library(shinycssloaders)
# Internal API key (replace with your actual API key)
OPENAI_API_KEY <- Sys.getenv("OPENAI_API_KEY")
ui <- page_fluid(
theme = bs_theme(version = 5),
useShinyjs(), # Add this to use shinyjs
br(),
titlePanel("AI Dataset Generator"),
sidebarLayout(
sidebarPanel(
textInput("description", "Describe the dataset you want",
placeholder = "e.g., health data for a family of 4"),
actionButton("generate", "Generate Dataset"),
hidden(downloadButton("download", "Download CSV")), # Hide download button initially
br(), br(),
uiOutput("summary"),
hr(),
tags$small("Note: Generated data may not be accurate or suitable for real-world use. The maximum number of records is limited to 25.")
),
mainPanel(
navset_tab(
nav_panel("Data Table",
br(),
DTOutput("dataset")
)
)
)
)
)
server <- function(input, output, session) {
dataset <- reactiveVal(NULL)
summary_text <- reactiveVal("")
preprocess_csv <- function(csv_string) {
# Extract only the CSV part
csv_pattern <- "(?s)(.+?\\n(?:[^,\n]+(?:,[^,\n]+)*\n){2,})"
csv_match <- str_extract(csv_string, csv_pattern)
if (is.na(csv_match)) {
stop("No valid CSV data found in the response")
}
lines <- str_split(csv_match, "\n")[[1]]
lines <- lines[lines != ""] # Remove empty lines
# Get the number of columns from the header
header <- str_split(lines[1], ",")[[1]]
num_cols <- length(header)
# Ensure all rows have the same number of columns
processed_lines <- sapply(lines[-1], function(line) { # Skip header
cols <- str_split(line, ",")[[1]]
if (length(cols) < num_cols) {
cols <- c(cols, rep("", num_cols - length(cols)))
} else if (length(cols) > num_cols) {
cols <- cols[1:num_cols]
}
cols
})
# Create a tibble
tibble(!!!setNames(as.list(as.data.frame(t(processed_lines))), header))
}
generate_summary <- function(df) {
prompt <- paste("Summarize the following dataset:\n\n",
"Dimensions: ", nrow(df), "rows and", ncol(df), "columns\n\n",
"Variables:\n", paste(names(df), collapse=", "), "\n\n",
"Please provide a brief summary of the dataset dimensions and variable definitions. Keep it concise, about 3-4 sentences.")
response <- POST(
url = "https://api.openai.com/v1/chat/completions",
add_headers(Authorization = paste("Bearer", OPENAI_API_KEY)),
content_type_json(),
body = toJSON(list(
model = "gpt-3.5-turbo-0125",
messages = list(
list(role = "system", content = "You are a helpful assistant that summarizes datasets."),
list(role = "user", content = prompt)
)
), auto_unbox = TRUE),
encode = "json"
)
if (status_code(response) == 200) {
content <- content(response)
summary <- content$choices[[1]]$message$content
return(summary)
} else {
return("Error generating summary. Please try again later.")
}
}
observeEvent(input$generate, {
req(input$description)
showPageSpinner()
prompt <- paste("Generate a fake dataset with at least two variables as a CSV string based on this description:",
input$description, "Include a header row. Limit to 25 rows of data. Ensure all rows have the same number of columns. Do not include any additional text or explanations.")
response <- POST(
url = "https://api.openai.com/v1/chat/completions",
add_headers(Authorization = paste("Bearer", OPENAI_API_KEY)),
content_type_json(),
body = toJSON(list(
model = "gpt-3.5-turbo-0125",
messages = list(
list(role = "system", content = "You are a helpful assistant that generates fake datasets."),
list(role = "user", content = prompt)
)
), auto_unbox = TRUE),
encode = "json"
)
if (status_code(response) == 200) {
content <- content(response)
csv_string <- content$choices[[1]]$message$content
tryCatch({
# Preprocess the CSV string and create a tibble
df <- preprocess_csv(csv_string) %>% clean_names() %>%
mutate(across(everything(), ~ ifelse(suppressWarnings(!is.na(as.numeric(.))), as.numeric(.), as.character(.))))
dataset(df)
updateSelectInput(session, "variable", choices = names(df))
# Generate and set summary
summary <- generate_summary(df)
summary_text(summary)
# Show download button
hidePageSpinner()
shinyjs::show("download")
}, error = function(e) {
showNotification(paste("Error parsing CSV:", e$message), type = "error")
})
} else {
showNotification("Error generating dataset. Please try again later.", type = "error")
}
# Hide loading spinner
shinyjs::hide("loading-spinner")
})
output$dataset <- renderDT({
req(dataset())
datatable(dataset(), rownames = FALSE, options = list(pageLength = 10))
})
output$download <- downloadHandler(
filename = function() {
"generated_dataset.csv"
},
content = function(file) {
req(dataset())
write.csv(dataset(), file, row.names = FALSE)
}
)
output$summary <- renderUI({
req(summary_text())
div(
h4("Dataset Summary"),
p(summary_text()),
style = "background-color: #f0f0f0; padding: 10px; border-radius: 5px;"
)
})
}
shinyApp(ui, server)To preview the app before you deploy, store your key in a file named .Renviron in your project, restart R, and then click Run App in RStudio.
.Renviron
OPENAI_API_KEY='your-api-key-here'
Add .Renviron to your .gitignore file so you do not commit your key:
.gitignore
.Renviron
Choose a deployment method
| Method | When to use it | Jump to |
|---|---|---|
| GitHub | Your code is on GitHub and you want it to update when you push (public repositories only on free accounts) | Deploy from GitHub |
| Posit Publisher | You work in Positron or VS Code and do not use GitHub | Deploy from Positron or VS Code |
| Push-button publishing | You work in RStudio and do not use GitHub | Deploy from RStudio |
| rsconnect | You want to deploy from the R console or automate deployments | Deploy from the command line |
For more on how these methods differ, see Publishing.
Deploy from GitHub
Use this method when your app’s code is in a GitHub repository and you want changes you push to publish automatically. On a free account, your repository must be public, so anyone can see your code.
Create a
manifest.jsonfile by running this in the R console from the folder that containsapp.R:rsconnect::writeManifest()List
.Renvironin.gitignoreso you do not commit your API key. Then commitapp.Randmanifest.jsonand push your app to a GitHub repository.Sign in to Connect Cloud.
Click Publish at the top of your Home page. If this is your first time publishing, Connect Cloud prompts you to install the GitHub App, which lets Connect Cloud read your repository.
Select Shiny.
Select your repository.
Check that Branch is the branch that has your content, usually
main.Select
app.Ras the Primary file.Leave Automatically publish on push on if you want Connect Cloud to update what you published each time you push to this branch. Turn it off to update only when you choose.
Click Advanced settings.
Click Add variable under Configure variables.
In the Name field, enter
OPENAI_API_KEY.In the Value field, enter your OpenAI API key.
Click Publish.
- Result: Connect Cloud shows a log of the install steps while it deploys. When it finishes, your app is live at an address like
https://[content-id].share.connect.posit.cloud, where[content-id]is a unique identifier that Connect Cloud picks. You can set a custom name to change the URL. - Update or redeploy: Commit and push to the connected branch and Connect Cloud republishes automatically. If you turned off automatic publishing, click Republish on the content’s card on your Home page. See Republishing.
For all options, see Publish from GitHub.
Deploy from Positron or VS Code
Use this method when you work in Positron or VS Code and want to deploy your app directly. Positron includes the Posit Publisher extension. In VS Code, install it from the marketplace first.
- Open your project folder in Positron, or in VS Code with Posit Publisher installed.
- Click the Posit Publisher icon in the Activity Bar.
- Next to CREDENTIALS, click + and select Posit Connect Cloud. A credential is a saved connection to your Connect Cloud account. Your browser opens.
- In your browser, check that the Authorize Access code matches the code in Positron or VS Code. Then click Continue and Authorize.
- Return to Positron or VS Code and accept or change the name for this credential. Do this once.
- Click the + next to Deployments. If Posit Publisher asks which file to publish, select
app.R. - Enter a title for your content, then select the credential you just added.
- Under PROJECT FILES, check that the list includes
app.Rbut not.Renviron. Connect Cloud uploads only the files in this list. - Under Secrets, add a secret named
OPENAI_API_KEYwith your API key as the value. - Click Deploy Your Project.
- Result: A success message appears in the lower right of Positron or VS Code. Click View Content in the extension to open the app on Connect Cloud. Its address has the form
https://[content-id].share.connect.posit.cloud, where[content-id]is unique to your app. - Update or redeploy: Save your changes, open Posit Publisher, select the previous deployment, and click Deploy Your Project again. Keep the
.positfolder in your project. It remembers which Connect Cloud content this project publishes to, so the next deploy updates the same content instead of creating a new one.
For the configuration file, multiple deployments per project, and troubleshooting, see Publish from your IDE.
Deploy from RStudio
Use this method when you work in RStudio and want to publish your app directly with push-button publishing.
- Open
app.Rin RStudio. - Click Publish in the editor toolbar.
- Follow the prompts to connect your Connect Cloud account and publish. Do not include
.Renvironin the files you publish. - Add your API key as a variable. Open the content’s content settings, click Add variable, enter
OPENAI_API_KEYas the name, and enter your API key as the value. Then republish the app so it picks up the key. Until then, the app shows an error.
- Result: RStudio opens your app on Connect Cloud.
- Update or redeploy: Save your changes and click Publish again.
For setup instructions and supported content types, see Publish from your IDE and the RStudio IDE publishing documentation.
Deploy from the command line
Use this method to script deployments from the R console with the rsconnect package. You do not need a manifest.json or renv.lock file for this method.
Install the package.
install.packages("rsconnect")Add your Connect Cloud account. Your browser opens for you to authorize access.
rsconnect::connectCloudUser()Deploy the folder that contains
app.R. Do not include.Renviron.rsconnect::deployApp()Add your API key as a variable. Open the content’s content settings, click Add variable, enter
OPENAI_API_KEYas the name, and enter your API key as the value. Then republish the app so it picks up the key. Until then, the app shows an error.
If your app is in a subdirectory, pass it with appDir, for example rsconnect::deployApp(appDir = "Dashboard"). If you have several accounts configured, rsconnect asks which one to use.
- Result: A successful deployment opens your browser to your content’s page on Connect Cloud.
- Update or redeploy: Run
rsconnect::deployApp()again.
For noninteractive credentials and other options, see Publish from your console or terminal.
After you deploy
Common things to set up next. They work the same however you deployed your app:
- Passwords and API keys: Change or add secret variables under Variables in content settings. The app reads the key with
Sys.getenv("OPENAI_API_KEY"). - Access and sharing: Control who can view your app in Sharing.
- Scheduling: On paid plans, schedule your app to republish at set times.
- Custom name and domain: Change the app’s URL with a custom name or a custom domain.
- Upload size limit: The upload size limit applies when you upload your project files from your editor or the R console.
Troubleshooting
- Error generating dataset or summary: The app shows these messages when OpenAI returns an error, for example when the key is missing or invalid. Check that the variable is named exactly
OPENAI_API_KEYand that its value is a valid key. - Missing package error: Re-run
rsconnect::writeManifest()somanifest.jsonlists every package your app loads, then commit it and republish. - API key committed by mistake: Revoke the key in your OpenAI account, create a new one, and store the new key as a variable on Connect Cloud.
- Repository not listed: Confirm that the GitHub App has access to it.
If your deployment still fails, read the log that Connect Cloud shows while it deploys and look for the first error. You can also try Error Assist, which gives guidance based on your log output.