URL-encoded forms

An URL-encoded form input/output can be specified in two ways. First, it is possible to map all form fields as a Seq[(String, String)], or Map[String, String] (which is more convenient if fields can’t have multiple values):

import sttp.tapir._

formBody[Seq[(String, String)]]: EndpointIO.Body[String, Seq[(String, String)]]
formBody[Map[String, String]]: EndpointIO.Body[String, Map[String, String]]

Second, form data can be mapped to a case class. The codec for the case class is automatically derived using a macro at compile-time. The fields of the case class should have types, for which there is a plain text codec. For example:

import sttp.tapir._

case class RegistrationForm(name: String, age: Int, news: Boolean, city: Option[String])

formBody[RegistrationForm]: EndpointIO.Body[String, RegistrationForm]

Each form-field is named the same as the case-class-field. The names can be transformed to snake or kebab case by providing an implicit tapir.generic.Configuraton, or customised using the @encodedName annotation.

Multipart forms

Similarly as above, multipart form input/outputs can be specified in two ways. To map to all parts of a multipart body, use:

import sttp.tapir._
import sttp.model.Part

multipartBody: EndpointIO.Body[Seq[RawPart], Seq[Part[Array[Byte]]]]

Part is a case class containing the name of the part, disposition parameters, headers, and the body. The bodies will be mapped as byte arrays (Array[Byte]). Custom multipart codecs can be defined with the Codec.multipartCodec method, and then used with multipartBody[T].

As with URL-encoded forms, multipart bodies can be mapped directly to case classes, however without the restriction on codecs for individual fields. Given a field of type T, first a plain text codec is looked up, and if one isn’t found, any codec for any media type (e.g. JSON) is searched for.

Each part is named the same as the case-class-field. The names can be transformed to snake or kebab case by providing an implicit sttp.tapir.generic.Configuraton, or customised using the @encodedName annotation.

Additionally, the case class to which the multipart body is mapped can contain both normal fields, and fields of type Part[T]. This is useful, if part metadata (e.g. the filename) is relevant.

For example:

import sttp.tapir._
import sttp.model.Part

case class RegistrationForm(userData: User, photo: Part[File], news: Boolean)
case class User(email: String)

multipartBody[RegistrationForm]: EndpointIO.Body[Seq[RawPart], RegistrationForm]