Learn R Programming

plumber2

This is a complete rewrite of plumber. The purpose of the rewrite is to take everything we’ve learned from plumber, shed the bad decision that you inevitably make over the course of development, and start from scratch.

You’ll find that plumber2 is very similar to plumber in a lot of ways, but diverts in key areas, resulting in API incompatibility between the two packages. Because of this you may need to update your plumber APIs if switching to plumber2.

Installation

plumber2 can be installed from CRAN using install.packages("plumber2"). Alternatively you can install the development version from github using pak

# install.packages("pak")
pak::pak("posit-dev/plumber2")

Hello World

Below is a simple “hello world” API written for plumber2 that illustrates some of the differences from plumber:

#* Echo the parameter that was sent in
#*
#* @get /echo/<msg>
#*
#* @param msg:string The message to echo back.
#*
#* @response 200:{msg:string} A string containing the input message
#*
function(msg) {
  list(
    msg = paste0("The message is: '", msg, "'")
  )
}

#* Plot out data from the palmer penguins dataset
#*
#* @get /plot
#*
#* @query spec:enum|Adelie, Chinstrap, Gentoo| If provided, filter the
#* data to only this species
#*
#* @serializer png{width = 700, height = 500}
#* @serializer jpeg{width = 700, height = 500}
#*
#* @async
function(query) {
  myData <- penguins
  title <- "All Species"

  # Filter if the species was specified
  if (!is.null(query$spec)){
    title <- paste0("Only the '", query$spec, "' Species")
    myData <- subset(myData, species == query$spec)
    if (nrow(myData) == 0) {
      abort_internal_error("Missing data for {query$spec}")
    }
  }

  plot(
    myData$flipper_len,
    myData$bill_len,
    main=title,
    xlab="Flipper Length (mm)",
    ylab="Bill Length (mm)"
  )
}

Above you can both see some breaking changes and some new features in action. The biggest breaking change is that parameters coming from the path, the query string, and the body are now clearly separated. Only parameters from the path are provided as direct arguments to the handler function. Query and body parameters are accessible through the query and body argument respectively. As can be seen above they also use different tags in the documentation.

Speaking of documentation, the parsing of plumber blocks have been greatly improved. It is now built upon roxygen2, so it follows that convention, allowing multiline tags and defaulting to the first line as title and proceeding untagged lines as description. The ability to define input and output types has also been greatly expanded, adding the ability to define nested objects, adding default values and (as seen above) define enum (factors) to name a few. All input will get type checked and default value imputed if missing.

For the /plot handler you can also see that it specifies multiple serializers. Doing so will allow the client to request its preferred response format using the Accept header. plumber2 will then perform content negotiation to figure out the best response format based on what it supports and what the client prefers.

Lastly, you can see a new tag (one of many) in the /plot handler. @async allows you to convert your handler into an async handler automatically. It is still possible to create an async handler manually by returning a promise, but the new tag significantly simplifies this for the most classic cases. There is still overhead involved in handling requests asynchronously so this is mainly a good idea for longer running handlers, but it is shown here as an example.

Copy Link

Version

Install

install.packages('plumber2')

Monthly Downloads

594

Version

0.2.0

License

MIT + file LICENSE

Issues

Pull Requests

Stars

Forks

Maintainer

Thomas Lin Pedersen

Last Published

January 20th, 2026

Functions in plumber2 (0.2.0)

Next

Router control flow
api

Create a new plumber API, optionally based on one or more plumber files
api_datastore

Persistent server-side data storage
api_auth

Add auth to an endpoint
api_auth_guard

Add an auth guard to your API
api_forward

Set up a plumber2 api to act as a reverse proxy
api_on

Add a handler to an event
api_package

Load up an API distributed with a package
api_message

Add a handler to a WebSocket message
api_logger

Set logging function and access log format for the API
api_redirect

Redirect request to another resource
api_run

Launch the API
api_request_header_handlers

Add a handler for a request header
api_request_handlers

Add a handler for a request
api_report

Serve Quarto and Rmarkdown documents from a plumber2 api
async_evaluators

Async evaluators provided by plumber
api_security_cors

Set up CORS for a path in your plumber2 API
api_security_headers

Add various security related headers to your plumber2 API
api_security_resource_isolation

Set up resource isolation for a path
api_session_cookie

Turn on session cookie data storage for your API
device_formatter

Create a graphics device formatter
get_opts

Retrieve options for creating a plumber2 api
api_shiny

Serve a Shiny app from a plumber2 api
apply_plumber2_block

Generic for applying information from a plumber2 block to an api
create_server_yml

Create a _server.yml file to describe your API
plumber2-package

plumber2: Easy and Powerful Web Servers
register_serializer

Register or fetch a serializer
register_parser

Register or fetch a parser
openapi

Construct OpenAPI specifications
parsers

Parser functions provided by plumber2
register_async

Register an async evaluator
serializers

Serializer functions provided by plumber2
parse_plumber_file

Parse a plumber file
init_formatter

Formatter orchestration
reexports

Objects exported from other packages
api_add_route

Add a new route to either the request or header router
Plumber2

The Plumber2 Class
api_assets

Serve resources from your file system
add_plumber2_tag

Add a tag extension to plumber2
api_docs

Configure your API for serving documentation for itself