This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Plumber is an R package that enables creating web APIs by decorating R functions with roxygen2-style comments. It provides annotation-driven API definition (#* @get /path), automatic OpenAPI spec generation, and built-in Swagger UI.
Current version: 1.3.0.9000 (development)
# Install development version
pak::pkg_install("rstudio/plumber")
# Load package for interactive development
devtools::load_all()
# Build and check package
devtools::check()
# Generate documentation from roxygen comments
devtools::document()
# Run all tests
devtools::test()
# Run specific test file
testthat::test_file("tests/testthat/test-plumber.R")
# Run tests matching a pattern
testthat::test_file("tests/testthat/test-plumber.R", filter = "routing")# Run an example API from inst/plumber/
pr("inst/plumber/10-welcome/plumber.R") %>% pr_run(port = 8000)
# Or for development testing
library(plumber)
pr("path/to/test-api.R") %>% pr_run(port = 8000)# Run full R CMD check
R CMD build . && R CMD check plumber_*.tar.gz
# Run tests only
Rscript -e "devtools::test()"
# Run specific test file
Rscript -e "testthat::test_file('tests/testthat/test-plumber.R')"The package uses R6 for object-oriented design with a clear inheritance chain:
Hookable (base class - provides hook system)
└── Plumber (main API router)
├── PlumberStatic (static file serving)
└── PlumberStep (execution lifecycle base)
├── PlumberEndpoint (terminal request handlers)
└── PlumberFilter (middleware)
Key files:
- R/hookable.R - Hook registration and execution (preroute, postroute, preserialize, etc.)
- R/plumber.R - Core
Plumberclass with routing engine - R/plumber-step.R -
PlumberStep,PlumberEndpoint,PlumberFilterclasses
Understanding the request flow is critical for debugging and extending the framework:
- Request arrives →
Plumber$call()invoked (R/plumber.R:565-846) - Pre-route hooks execute (auth, logging, request modification)
- Route matching in priority order:
"__first__"preempted endpoints (highest priority)- Filters execute sequentially with
forward()continuation - Endpoints checked at each preemption level
- Mounted sub-routers (
pr_mount()) - 404 handler if no match
- Post-route hooks execute (after handler, before serialization)
- Serialization:
- Pre-serialize hooks (modify response object)
- Serializer function application
- Post-serialize hooks (modify final output)
- HTTP response returned
Forward mechanism: Filters must call forward() to pass control to the next handler. This uses an execution domain pattern tracked internally.
APIs are defined using special comments that get parsed into route metadata:
Core annotations:
#* @get /path,#* @post /path- HTTP verb + route#* @serializer json- Output format (json, html, png, csv, etc.)#* @parser json- Input parsing (json, form, multipart, etc.)#* @param name:type Description- Parameter documentation#* @filter name- Define filter/middleware#* @preempt [filter_name]- Control execution order#* @plumber- Router-level configuration
Implementation:
- R/plumb-block.R -
plumbBlock()scans backward from function to find#*comments - Uses regex to parse structured tags into metadata
evaluateBlock()createsPlumberEndpointorPlumberFilterobjects- Supports both
#*(recommended) and#'prefixes
Three extensible registries in .globals environment:
-
Serializers (R/serializer.R) - Transform R objects to HTTP responses
- 28+ built-in: json, html, png, csv, geojson, arrow, parquet, xlsx, etc.
- Composable via
serializer_headers()→serializer_content_type()→ specific serializers - Register custom:
register_serializer("name", func, contentType)
-
Parsers (R/parse-body.R, R/parse-query.R) - Parse request bodies
- Query string:
parseQS()handles URL parameters with encoding - Body parsing: Content-Type detection, multipart form support
- Register custom:
register_parser("name", func, contentType)
- Query string:
-
Docs (R/ui.R) - Visual API documentation providers
- Built-in: swagger, rapidoc, redoc
- Mounted at
/__docs__/by default - OpenAPI spec at
/openapi.json
Automatic OpenAPI 3.0 spec generation from annotations:
- R/openapi-spec.R -
$getApiSpec()generates full spec - R/openapi-types.R - Type inference from R function signatures
- Parameter metadata extracted from
@paramtags - Response types inferred from serializers
pr_set_api_spec()for manual spec customization
Built on the promises package for non-blocking execution:
- R/async.R -
runSteps()andrunStepsUntil()handle sync/async - Promise detection via
is.promising(result) - Recursive step execution for async handlers
- Error handling with
%...!%catch operator - Filters and endpoints can return promises
Regex-based path matching with type conversion:
#* @get /users/<id:int>
#* @get /files/<name:string>
#* @get /data/<vals:[int]> # Array of integersSupported types: int, double/numeric, bool/logical, string, arrays [type]
Implementation in R/plumber.R - path regex compilation and parameter extraction.
R requires specific file load order due to dependencies. The Collate field enforces:
- Base classes first:
async.R,hookable.R - Core infrastructure:
plumber.R,plumber-step.R - Features: parsers, serializers, OpenAPI
- Utilities:
utils.R,zzz.R(last - package hooks)
When adding new files, consider load order dependencies.
Core routing & execution:
- R/plumber.R - Main
Plumberclass (845 lines) - R/plumber-step.R - Step/Endpoint/Filter classes
- R/hookable.R - Hook system base class
- R/async.R - Promise/async execution engine
API definition:
- R/plumb.R -
plumb()entry point - R/plumb-block.R - Annotation parser
- R/pr.R - Programmatic API (
pr()constructor) - R/pr_set.R - Configuration methods (
pr_set_*())
Data handling:
- R/serializer.R - Output serialization (28+ formats)
- R/parse-body.R - Request body parsing
- R/parse-query.R - Query string parsing
OpenAPI/Documentation:
- R/openapi-spec.R - OpenAPI 3.0 generation
- R/openapi-types.R - Type inference
- R/ui.R - Swagger/Rapidoc/Redoc UI mounting
Advanced features:
- R/session-cookie.R - Encrypted session cookies
- R/plumber-static.R - Static file serving
- R/shared-secret-filter.R - Authentication
- R/default-handlers.R - 404/405/500 handlers
Tests in tests/testthat/ follow clear naming:
- Unit tests:
test-<feature>.R(e.g.,test-serializer.R,test-parser.R) - Integration tests:
test-<component>.R(e.g.,test-plumber.R,test-endpoint.R) - Helper files:
helper-<utility>.R(e.g.,helper-mock-request.R)
Use make_req() from tests/testthat/helper-mock-request.R to create test requests:
req <- make_req(verb = "POST", path = "/api/data", qs = "param=value", body = '{"key":"value"}')17 complete example APIs in inst/plumber/ demonstrate all features:
01-append- Basic GET/POST02-filters- Middleware patterns06-sessions- Session management12-entrypoint- Entrypoint pattern for complex APIs13-promises,14-future- Async examples15-openapi-spec- OpenAPI customization
Run examples with: pr("inst/plumber/<example>/plumber.R") %>% pr_run()
Plumber supports both annotation-based and programmatic API definition:
Annotation-based (declarative):
#* @get /hello
function(name = "world") {
list(message = paste("Hello", name))
}Programmatic (imperative):
pr() %>%
pr_get("/hello", function(name = "world") {
list(message = paste("Hello", name))
})Both approaches are first-class. Use annotations for file-based APIs, programmatic for dynamic construction.
Complex APIs can be composed from multiple routers:
root <- pr()
users_api <- pr("apis/users.R")
products_api <- pr("apis/products.R")
root %>%
pr_mount("/users", users_api) %>%
pr_mount("/products", products_api) %>%
pr_run()Mounted routers maintain independent configurations (hooks, error handlers).
For APIs requiring initialization, use entrypoint.R that returns a configured router:
# inst/plumber/12-entrypoint/entrypoint.R
function(port = 8000) {
pr("plumber.R") %>%
pr_set_debug(TRUE) %>%
pr_cookie(secret_key) %>%
pr_hook("preroute", logging_hook)
}This pattern enables environment-specific configuration and dependency injection.
Set via options() or environment variables:
plumber.port,plumber.host- Default server bindingplumber.maxRequestSize- Max body size (bytes)plumber.docs- Enable/disable documentation UIplumber.docs.callback- Custom docs providerplumber.apiURL,plumber.apiPath- OpenAPI spec URLs
See R/options_plumber.R for full list.
GitHub Actions workflow (.github/workflows/R-CMD-check.yaml):
- Runs on push to main, PRs, and weekly (Monday 7am)
- Three jobs: website build, routine checks, R CMD check
- Uses rstudio/shiny-workflows templates
Annotations are parsed top-to-bottom. Place route-modifying annotations before the HTTP verb:
#* @serializer json # ✓ Correct order
#* @get /dataNot:
#* @get /data
#* @serializer json # ✗ Won't apply to this endpointFilters MUST call forward() to continue the pipeline:
#* @filter logger
function(req) {
log(req$PATH_INFO)
forward() # Required! Without this, request stops here
}Extract path params with type conversion:
#* @get /users/<id:int> # ✓ Type specified
function(id) {
# id is already an integer
}Without type hint, all params are strings.
- Serializer - Converts R object → HTTP response (output)
- Parser - Converts HTTP request → R object (input)
Use @serializer for response format, @parser for request body format.
- Website: https://www.rplumber.io
- Vignettes: vignettes/ - 11 comprehensive guides
- Man pages: man/ - Generated from roxygen2 comments
- Cheat sheet: https://github.com/rstudio/cheatsheets/blob/main/plumber.pdf
When adding new features, update corresponding vignette and man page.