sdstudio
A Companion Package for Designing
and Managing surveydown Surveys


Pingfan Hu
George Washington University

GW OSCON 2026

About Me

  • Pingfan Hu, PhD Candidate at George Washington University, advised by Dr John Helveston.

  • Research field: Sustainable transportation

  • Research focuses:

    1. Open-source software development
    2. Data science and choice modeling
  • For more infomation, visit pingfanhu.com

Recap of surveydown

I try to figure out what people want…

 

Prof. John Helveston

WYSIWYG Interface

Limitations


❌ Reproduciblity

❌ Version control

Limitations


❌ Reproduciblity

❌ Version control

❌ Limited features

❌ Open source



Why not make surveys from code?

Limitations
Features

✅ Reproducibility

✅ Version control

✅ Lots of features

✅ Open source

Introducing surveydown!


In this talk, we will cover:


What is surveydown?

How does it work?

What can I do with it?

What’s next?

What is surveydown?


R package that renders Quarto files into surveys


Quarto is a publishing system


Original qmd file



Markdown + R code chunks

---
format: html
title: "HTML Page with R Code"
---

# Hello, World!

This is a simple **HTML** page with *R* code.

```{r}
library(ggplot2)

df <- data.frame(x = rnorm(100))
ggplot(df, aes(x = x)) +
  geom_histogram()
```

Rendered HTML


Original qmd file



Markdown + Python code chunks

---
format: pdf
title: "PDF File with Python Code"
---

# Hello, World!

This is a simple **PDF** file with *Python* code.

```{python}
import matplotlib.pyplot as plt
import numpy as np

data = np.random.normal(0, 1, 100)
plt.hist(data)
plt.show()
```

Rendered PDF


survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Rendered Survey


survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

YAML header for a “clean” output

survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Load the surveydown Package

survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Use triple-dash marker (---)

to define survey pages

Quarto fences (:::) also OK:


::: {#welcome .sd-page}


Page content


:::

survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Page content

  • Markdown for texts, images, etc.
  • sd_question() for survey questions
  • sd_next() for page navigation

survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Rendered Survey


survey.qmd

---
format: html
echo: false
warning: false
---

```{r}
library(surveydown)
```

--- welcome

# Welcome to `surveydown`!

```{r}
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite super hero?",
  option = c(
    "Yes" = "yes",
    "No"  = "no"
  )
)

sd_next()
```

--- some_other_page

Other content...

Rendered Survey

Wait a minute…
Quarto renders to static html pages, right?


Shiny to the rescue!


A complete surveydown survey


survey.qmd

A Quarto doc defining the survey content (pages, texts, images, questions, etc).

app.R

An R script defining the survey Shiny app.

Don’t panic!


app.R


library(surveydown)

ui <- sd_ui()
  
server <- function(input, output, session) { 

  sd_server()

} 

shiny::shinyApp(ui = ui, server = server)

app.R


library(surveydown)

ui <- sd_ui()
  
server <- function(input, output, session) { 

  sd_server()

} 

shiny::shinyApp(ui = ui, server = server)

Render survey.qmd as UI

Run the surveydown server

Wait a minute…
How do you store the response data?


PostgreSQL to the rescue!



supabase.com

app.R


library(surveydown)

# sd_db_config()
db <- sd_db_connect()

ui <- sd_ui()
  
server <- function(input, output, session) { 

  sd_server(db)

} 

shiny::shinyApp(ui = ui, server = server)

Store credentials

Connect to the database

Pass connection to sd_server()

What is surveydown?


What can surveydown do?

surveydown is feature-packed!

Feature Highlights


Question types

Conditional logic

12 Built-in Question Types

text

textarea

numeric

mc

mc_multiple

mc_buttons

mc_multiple_buttons

select

slider

slider_numeric

date

daterange

Question Type: text


sd_question(
  type  = "text",
  id    = "fav_hero_name",
  label = "Who is your favorite super hero?"
)


–>

Question Type: mc_buttons



sd_question(
  type  = "mc_buttons",
  id    = "dream_power",
  label = "If you could have ONE superpower?",
  option = c(
    "🕸️ Web-slinging"   = "webslinging",
    "🛡️ Super Strength" = "strength",
    "✈️ Flight"         = "flight",
    "🧠 Telepathy"      = "telepathy",
    "⚡️ Super Speed"    = "speed"
  ),
  direction = "vertical"
)





–>

Conditional logic


Conditional showing

Conditional skipping

Conditional stopping

Conditional showing - sd_show_if()


Conditional showing - sd_show_if()


survey.qmd

# Conditional Question
sd_question(
  type  = "mc",
  id    = "has_fav_hero",
  label = "Do you have a favorite hero?",
  option = c("Yes" = "yes", "No" = "no")
)

# Target Question
sd_question(
  type  = "text",
  id    = "fav_hero",
  label = "Who is your favorite super hero?"
)

app.R

# Inside server
sd_show_if(
  input$has_fav_hero == "yes" ~ "fav_hero"
)

Condition ~ Target


“If {condition}, then show {target}”

If it works in Shiny,
it works in surveydown.

Embedding an interactive map with leaflet:


surveydown + LLMs
is pretty cool

Generate surveys with LLMs!

Introducing sdstudio!

Studio Workflow

Launch with a simple command


sdstudio::launch()

Landing Page

Start with a Template

surveydown.org/templates

Choose Your Directory

The Build Tab

The Preview Tab

The Responses Tab - Local Mode

The Responses Tab - DB Mode

Studio Features

GUI Operations

(Image here)

Side-by-side Code Scripts

(Image here)

Modes and Views

Local Mode vs Database Mode

Desktop View vs Mobile View

Accessibility

Survey preview

Response dashboard

PostgreSQL connection

Data file view & download

With great power comes great responsibility.

Ways to help:



  • Try it out!
  • Give it a ⭐
  • Post an issue
  • Join the GitHub discussion
  • Contribute a template

Meet the team!

John Helveston, Ph.D.

Pingfan Hu

Bogdan Bunea