Skip to contents

Annotations

Annotations are specially-structured comments used in your plumber file to create an API. A full annotation line starts with #*, then the annotation keyword @..., any number of space characters followed by the content. If you wish to use the annotation to document your API file but don’t want any OpenAPI documentation to be generated you can use @noDoc tag which works much like roxygens @noRd.

Global annotations

Global annotations are not related to any handler and should be placed in their own block. The block should be terminated by a NULL expression. Instead of @apiTitle and @apiDescription you can also use the convention that the first line gives the title and any proceeding lines until the first tag gives the description.

Annotation Argument Description/References
@apiTitle Title Info Object
@apiDescription Description Info Object
@apiTOS TOS link Info Object
@apiContact Contact object Contact Object
@apiLicense License object License Object
@apiVersion Version Info Object
@apiTag Tag Description Can be repeated to add multiple tags. Quote with ” or ’ to use non word character (like spaces) in Tag. Tag Object
Annotations example
#* Sample Pet Store App
#*
#* This is a sample server for a pet store.
#*
#* @apiTOS http://example.com/terms/
#* @apiContact list(name = "API Support", url = "http://www.example.com/support", email = "support@example.com")
#* @apiLicense list(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html")
#* @apiVersion 1.0.1
#* @apiTag pet Pets operations
#* @apiTag toy Toys operations
#* @apiTag "toy space" Toys operations
NULL
Equivalent programmatic usage
api() |>
  api_doc_add(list(
    info = list(
      title = "Sample Pet Store App",
      description = "This is a sample server for a pet store.",
      termsOfService = "http://example.com/terms/",
      contact = list(name = "API Support", url = "http://www.example.com/support", email = "support@example.com"),
      license = list(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html"),
      version = "1.0.1"
    ),
    tags = list(
      list(name = "pet", description = "Pets operations"),
      list(name = "toy", description = "Toys operations"),
      list(name = "toy space", description = "Toys operations")
    )
  ))

Handler annotations

Handler annotation describe all aspects of a request handler and always proceeds a function which is considered the handler function. The following tags can be used in a handler block. The first line, unless it has a tag is considered the title of the handler and any proceeding lines until the first tag is considered a long-form description.

Endpoint

Annotation

Argument

Description/References

@get, @head, @post, @put, @delete, @connect, @options, @trace, @patch, @any

Path

Endpoints, Dynamic Routes, Typed Dynamic Routes

@header

None

Should the handler be attached to the header router

@serializer

Alias[{Args list]}]

Mime Function

Some serializers accept arguments. See serializers article and serializers reference. Aliases : r paste0(“<code>”, registered_serializers(), “</code>”, collapse = “,”) from registered_serializers().

@serializerStrict

None

Turn on strict content negotiation. By default, the first serializer is chosen if the client requests a response type that isnt supported. By using strict content negotiation a 406 response is sent if the requested response type is not available

@parser

Alias[{Args list}]

Mime Function

Some parsers accept arguments. See parsers reference. Can be repeated to allow multiple parsers on the same endpoint. Aliases : r paste0(“<code>”, registered_parsers(), “</code>”, collapse = “,”) from registered_parsers().

@param, @query, @body

Name[:Type][(Default)][*][Description]

Adding an asterix indicates that the parameter is required. Can be repeated to define different parameters. If a single @body tag is used the Name can be omitted to indicate that the body is not a named object but simply an instance of the given Type

@response

Status[:Type] Description

Simple Response object. Can be repeated to define different responses.

@download

[Filename]

Mark the response as something that should be downloaded, optionally setting a default filename for the file

@tag

Tag

Can be repeated to add multiple tags. Quote with or to use non word character (like spaces) in Tag. Tag field

@message

None

Marks the handler as a WebSocket message handler. No other tags will have an effect if this tag is present

More details on Type

Types are used to define API inputs and outputs. For path parameters they can be given both in @param and inside the handler path. If they are given both places they must be in agreement. For query and body parameters they are given in their respective @query and @body tags.

Some types can have a nested structure which is also supported, but the type spec can quickly become difficult to read the further you recurse so use with care

Type OpenAPI
boolean boolean
number number
integer integer
string string
date string format:date
date-time string format:date-time
byte string format:byte
binary string format:binary
[Type] array items:type:Type
{prop_name: Type, prop_name2: Type2} object properties:prop_name:type:Type properties:prop_name2:type:Type2

Types can have a default value, which is given in parentheses after the type specification, e.g. integer(10). For objects and arrays you should use JSON notation to describe the default value (e.g. [integer]([2, 6, 1])).

For the integer and number types it is also possible to specify the lower and/or upper bounds of the value. This is done by putting these between | like so: integer|3, 7|. Omitting one of these will remove that bound requirement (e.g. integer|,7| only requires the input to be below or equal to 7). If combining this with a default value the range comes first (integer|3,7|(5)).

parameters can be specified as optional or required in their type notation. Path parameters are always required so any setting is ignored for this. A parameter can be marked as required by adding a * suffix to the type description, e.g. arg1:integer* to indicate that arg1 is required and an integer. A parameter cannot both be required and have a default value (for reasons of logic).

Apart from being used in the documentation of your API, the type information you provide for parameters will also be used to cast the incoming values to the correct type and add defaults if missing. Further, missing required parameters will result in an error. The response is not type checked so it is up to you to conform with the specification you provide.

Annotations example
#* @get /query/parameters
#* @serializer text
#* @query name:string*
#* @query age:integer*
function(query) {
  sprintf("%s is %i years old", query$name, max(query$age))
}

#* @get /dyn/<name:string>/<age:integer>/route
#* @serializer text
#* @parser none
#* @response 200:string A sentence
function(name, age) {
  sprintf("%s is %i years old", name, age)
}

#* Upload a file and return the object as an rds
#*
#* @post /upload_file
#* @serializer rds
#* @parser multi
#* @body file:binary A file
#* @download
function(body) {
  body$file
}

#* @message
function(message, client_id, server) {
  if (is.character(message)) {
    server$log("message", paste0(client_id, " says ", message))
  }
  NULL
}
Equivalent programmatic usage
text_handler <- function(name, age) {
  sprintf("%s is %i years old", name, max(age))
}
qp_handler <- function(query) {
  text_handler(query$name, query$age)
}
file_handler <- function(body) {
  body$f
}
msg_handler <- function(message, client_id, server) {
  if (is.character(message)) {
    server$log("message", paste0(client_id, " says ", message))
  }
  NULL
}

api() |>
  api_get(
    path = "/query/parameters",
    handler = qp_handler,
    serializers = get_serializers("text"),
    parsers = get_parsers(),
    doc = list(
      parameters = list(
        list(
          `in` = "query",
          name = "name",
          schema = list(type = "string"),
          required = TRUE
        ),
        list(
          `in` = "query",
          name = "age",
          schema = list(type = "integer"),
          required = TRUE
        )
      )
    )
  ) |>
  api_get(
    path = "/dyn/<name:string>/<age:integer>/route",
    handler = text_handler,
    serializers = get_serializers("text"),
    doc = list(
      responses = list(
         "200" = list(
          description = "A sentence",
          content = list(
            "text/plain" = list(schema = list(
              type = "string"
            ))
          )
        )
      )
    )
  ) |>
  api_post(
    path = "/upload_file",
    handler = file_handler,
    serializers = get_serializers("rds"),
    parsers = get_parsers("multi"),
    doc = list(
      description = "Upload an rds file and return the object",
      requestBody = list(
        content = list(
          "multipart/form-data" = list(schema = list(
            type = "object",
            properties = list(
              file = list(
                type = "string",
                format = "binary"
              )
            )
          ))
        )
      )
    )
  ) |>
  api_message(msg_handler)

Asset annotation

There are two ways to serve static content in plumber2 and they differ in subtle ways. The @assets tag instruct plumber to create a regular handler that matches the mount path (defaults to /) and will serve files from Path. The @statics tag works the same, but rather than create a handler it instructs httpuv (which is the low-level package powering plumber2) to serve the files before the request even reaches the R process. This makes it much faster but also limited in flexibility since the request never reaches your code and you are unable to modify it. In general, you should use @statics unless you need to provide any additional handling of the request, such as authentication or logging.

Annotation Arguments Description/References
@assets Path [Mount path] Static files
@statics Path [Mount path]
@except Path Can be used together with @statics to exclude subpaths of the @statics Path from being served.
Annotations example
#* @assets ./assets/files
NULL

#* @assets ./assets/files /assets
NULL

#* @statics ./assets/static_files
#* @except /secret_files
NULL
Equivalent programmatic usage (note that argument order is reversed)
api() %>%
  api_assets("/", "./assets/files")

api() %>%
  api_assets("/assets", "./assets/files")

api() %>%
  api_statics("/", "./assets/static_files", except = "/secret_files")

Plumber annotation

Annotation Arguments Description/References
@plumber None Modify plumber router from plumber file. The plumber router provided to the function must be returned. In most cases, anonymous functions are used following the #* @plumber annotation. However, named functions can also be used. When a named function is used, it must be referenced without parentheses.
Annotations example
#* @plumber
function(api) {
  api %>%
    api_doc_setting("swagger")
}

# Named function
use_swagger <- function(api) {
  api %>%
    api_doc_setting("swagger")
}

#* @plumber
use_swagger
Equivalent programmatic usage
api() |>
  api_doc_setting("swagger")