Appendix C — Plumber vs. Plumber2 Syntax

Published

2026-10-23

Overview

plumber and plumber2 both turn R functions into web APIs, and both define routes with #* annotation comments. plumber2 changes what the handler receives (a parsed body and query instead of req), renames the response object, and adds api(), api_get(), and api_post() for building routers in code.

Syntax Comparison

1. Basic Routing

Plumber

In plumber, annotations define the HTTP method, path, and parameters, and each parameter arrives as a function argument:

#* @get /hello
#* @param name The name to greet
function(name = "World") {
  list(message = paste("Hello", name))
}

Plumber2

plumber2 keeps the annotations. Query parameters are declared with @query (with a type) and arrive together in the query argument:

#* @get /hello
#* @query name:string The name to greet
function(query) {
  list(message = paste("Hello", query$name))
}

2. Request Bodies and Errors

Plumber

In plumber, the handler reads the request through req and sets status codes on res:

#* @post /predict
function(req, res) {
  data <- req$body
  if (!is.data.frame(data)) {
    res$status <- 400
    return(list(error = "Input must be a data frame"))
  }
  # ... prediction logic
}

Plumber2

plumber2 parses the JSON body before the handler runs and passes it in as body; response replaces res:

#* @post /predict
#* @serializer json
function(body, response) {
  if (!is.data.frame(body)) {
    response$status <- 400L
    return(list(error = "Input must be a data frame"))
  }
  # ... prediction logic
}

3. Summary Table

Feature plumber plumber2
Route definition #* annotations or pr_get()/pr_post() #* annotations or api_get()/api_post()
Router object pr() api()
Request data Arguments and req$body Parsed query and body arguments
Response object res response
Parameter types Documented with @param Declared with @query name:type