HORIZON HASKELLDocslts/ghc-9.14.x3e2d5682026-10-11Search names, modules, packages, or :: a typeCtrl K

GHC 9.14.1 · lts/ghc-9.14.x · 3e2d568 · 2026-10-11

Moduleservant-multipart-0.13.0Haskell2010

Servant.Multipart

multipart/form-data server-side support for servant. See servant-multipart-api for the API definitions.

  • 12 types
  • 4 classes
  • 8 values
typetype MultipartForm tag a = MultipartForm' '[] tag a
#

Combinator for specifying a multipart/form-data request body, typically (but not always) issued from an HTML <form>.

multipart/form-data can't be made into an ordinary content type for now in servant because it doesn't just decode the request body from some format but also performs IO in the case of writing the uploaded files to disk, e.g in /tmp, which is not compatible with servant's vision of a content type as things stand now. This also means that MultipartForm can't be used in conjunction with ReqBody in an endpoint.

The tag type parameter instructs servant-multipart to handle data either as data to be saved to temporary storage (Tmp) or saved to memory (Mem).

The a type parameter represents the Haskell type to which you are going to decode the multipart data to, where the multipart data consists in all the usual form inputs along with the files sent along through <input type="file"> fields in the form.

One option provided out of the box by this library is to decode to MultipartData.

Example:

  type API = MultipartForm Tmp (MultipartData Tmp) :> Post '[PlainText] String

  api :: Proxy API
  api = Proxy

  server :: MultipartData Tmp -> Handler String
  server multipartData = return str

    where str = "The form was submitted with "
             ++ show nInputs ++ " textual inputs and "
             ++ show nFiles  ++ " files."
          nInputs = length (inputs multipartData)
          nFiles  = length (files multipartData)
  

You can alternatively provide a FromMultipart instance for some type of yours, allowing you to regroup data into a structured form and potentially selecting a subset of the entire form data that was submitted.

Example, where we only look extract one input, username, and one file, where the corresponding input field's name attribute was set to pic:

  data User = User { username :: Text, pic :: FilePath }

  instance FromMultipart Tmp User where
    fromMultipart multipartData =
      User <$> lookupInput "username" multipartData
           <*> fmap fdPayload (lookupFile "pic" multipartData)

  type API = MultipartForm Tmp User :> Post '[PlainText] String

  server :: User -> Handler String
  server usr = return str

    where str = unpack (username usr) ++ "'s profile picture"
             ++ " got temporarily uploaded to "
             ++ pic usr ++ " and will be removed from there "
             ++ " after this handler has run."
  

Note that the behavior of this combinator is configurable, by using serveWithContext from servant-server instead of serve, which takes an additional Context argument. Context is a heterogeneous list where you can for example store a value of type MultipartOptions from servant-multipart that has the configuration that you want, which would then get picked up by servant-multipart.

Important: as mentioned in the example above, the file paths point to temporary files which get removed after your handler has run, if they are still there. It is therefore recommended to move or copy them somewhere in your handler code if you need to keep the content around.

Important: the input names, input values, file names and file content types of the submitted form must all be valid UTF-8, including those of the parts that your FromMultipart instance ignores. A form that carries any other encoding is rejected with a 400 response before your handler runs, unless Lenient is used. The contents of the uploaded files are not decoded and may be arbitrary bytes.

datadata MultipartForm' (mods :: [Type]) tag a
#

MultipartForm which can be modified with Lenient.

Under Lenient, the handler is passed an Either CheckError a from servant-multipart rather than the request being rejected, so it is handed the message from a failed fromMultipart call, from a form whose text is not valid UTF-8, or from a form that exceeds a body parsing limit.

Instances7HasForeign, HasLink, HasDocs, HasServer, MkLink, ServerT, …
datadata MultipartData tag
#

What servant gets out of a multipart/form-data form submission.

The type parameter tag tells if MultipartData is stored as a temporary file or stored in memory. tag is type of either Mem or Tmp.

The inputs field contains a list of textual Inputs, where each input for which a value is provided gets to be in this list, represented by the input name and the input value. See haddocks for Input.

The files field contains a list of files that were sent along with the other inputs in the form. Each file is represented by a value of type FileData which among other things contains the path to the temporary file (to be removed when your handler is done running) with a given uploaded file's content. See haddocks for FileData.

Constructors

Instances7FromMultipart, ToMultipart, Eq, Show, Semigroup, Monoid, …
classclass FromMultipart tag a where
#

MultipartData is the type representing multipart/form-data form inputs. Sometimes you may instead want to work with a more structured type of yours that potentially selects only a fraction of the data that was submitted, or just reshapes it to make it easier to work with. The FromMultipart class is exactly what allows you to tell servant how to turn "raw" multipart data into a value of your nicer type.

  data User = User { username :: Text, pic :: FilePath }

  instance FromMultipart Tmp User where
    fromMultipart form =
      User <$> lookupInput "username" form
           <*> fmap fdPayload (lookupFile "pic" form)
  

Methods

  • fromMultipart :: MultipartData tag -> Either String a

    Given a value of type MultipartData, which consists in a list of textual inputs and another list for files, try to extract a value of type a. When extraction fails, servant errors out with status code 400.

Instances1FromMultipart
valuelookupAllInputs :: Text -> MultipartData tag -> [Text]
#

Lookup all textual inputs with the given name attribute.

Takes linear time with respect to the number of inputs.

This function returns a list of all values for inputs with the specified name. It is useful when handling forms that allow multiple inputs with the same name, such as multiple select inputs or checkbox groups with explicit values.

Example:

let mpd = MultipartData [Input "color" "red", Input "color" "blue"] []
lookupAllInputs "color" mpd == ["red", "blue"]
lookupAllInputs "size"  mpd == []
valuelookupAllFiles :: Text -> MultipartData tag -> [FileData tag]
#

Lookup all file inputs with the given name attribute.

Takes linear time with respect to the number of files.

This function returns a list of all files uploaded under the specified name. It is useful when handling forms that allow multiple file uploads with the same name, such as file inputs with the multiple attribute.

Example:

let file1 = FileData "file" "doc1.pdf" "applicationpdf" "tmp/doc1"
    file2 = FileData "file" "doc2.pdf" "applicationpdf" "tmp/doc2"
    mpd   = MultipartData [] [file1, file2] :: MultipartData Tmp
lookupAllFiles "file"  mpd == [file1, file2]
lookupAllFiles "image" mpd == []

Lookup a textual input with the given name attribute and parse it into the desired type.

Takes linear time with respect to the number of inputs.

This function returns the parsed value if the input exists and can be parsed successfully using its FromHttpApiData instance. If the input is not found or parsing fails, it returns an error message.

Note: This function requires the field to be present in the request. Standalone HTML boolean checkboxes (which submit "on" when checked and are omitted by browsers when unchecked) are not directly supported by 'FromHttpApiData Bool'; check for presence with lookupInput or lookupAllInputs instead, or use a custom newtype with a FromHttpApiData instance.

Example:

let mpd = MultipartData [Input "age" "30"] []
lookupInputAs "age"     mpd == Right (30 :: Int)
lookupInputAs "isAdmin" mpd == (Left "Field isAdmin not found" :: Either String Int)

Lookup all textual inputs with the given name attribute and parse them into the desired type.

Takes linear time with respect to the number of inputs.

This function returns a list of parsed values for inputs with the specified name using their FromHttpApiData instance. It is useful for forms with repeated fields, multiple select inputs, or checkbox groups sharing the same name with explicit values.

If no inputs are found, an empty list is returned. If parsing fails for any value, an error message is returned.

Example:

let mpd = MultipartData [Input "nums" "1", Input "nums" "2"] []
lookupAllInputsAs "nums" mpd == Right [1, 2 :: Int]
lookupAllInputsAs "size" mpd == Right ([] :: [Int])
datadata CheckError
#

Why a multipart/form-data request body was not decoded. Under Lenient, the handler is passed this instead of the request being rejected.

Constructors

Instances3Eq, Show, NFData
  • Eq CheckErrorDefined in servant-multipart-0.13.0 · Servant.Multipart
  • Show CheckErrorDefined in servant-multipart-0.13.0 · Servant.Multipart
  • NFData CheckErrorDefined in servant-multipart-0.13.0 · Servant.Multipart
datadata LimitExceeded
#

A multipart/form-data request body that exceeds one of the generalOptions limits.

Constructors

Instances3Eq, Show, NFData
datadata MultipartOptions tag
#

Global options for configuring how the server should handle multipart data.

generalOptions lets you specify mostly multipart parsing related options, such as the maximum file size, while backendOptions lets you configure aspects specific to the chosen backend. Note: there isn't anything to tweak in a memory backend (Mem). Maximum file size etc. options are in ParseRequestBodyOptions.

See haddocks for ParseRequestBodyOptions and TmpBackendOptions respectively for more information on what you can tweak.

A form that exceeds one of the generalOptions limits is rejected before the handler runs, unless Lenient is used, in which case the handler is passed a LimitError. The response is built by the ErrorFormatters in the context, if any, like other request body errors, except that exceeding a size limit always responds with status 413 and exceeding a part header limit always responds with status 431.

Default configuration for multipart handling.

Uses defaultBackendOptions, and defaultParseRequestBodyOptions with a per-file size limit added. The per-file limit is set here, and the others are the defaults of wai-extra 3.1.18. The resulting limits are:

  • at most 25 MiB (25 * 1024 * 1024 bytes) per file

  • at most 10 files

  • no limit on the total size of all files

  • at most 65336 bytes of textual inputs in total

  • input and file input names of at most 32 bytes

  • at most 32 header lines per part, each of at most 8190 bytes

Since the total size of all files is not limited, a single request can carry up to 250 MiB of files, which the Mem backend holds in memory and the Tmp backend writes to disk. Use setMaxRequestFileSize, setMaxRequestFilesSize, setMaxRequestNumFiles and the other setters from Network.Wai.Parse on generalOptions to change these limits, or noLimitParseRequestBodyOptions to remove them.

datadata Mem
#

Tag for data stored in memory

Instances3MultipartBackend, MultipartResult, MultipartBackendOptions
datadata Input
#

Representation for a textual input (any <input> type but file).

<input name="foo" value="bar" /> would appear as Input "foo" "bar".

Constructors

Instances3Eq, Show, NFData
  • Eq InputDefined in servant-multipart-api-0.13.0 · Servant.Multipart.API
  • Show InputDefined in servant-multipart-api-0.13.0 · Servant.Multipart.API
  • NFData InputDefined in servant-multipart-api-0.13.0 · Servant.Multipart.API
datadata FileData tag
#

Representation for an uploaded file, usually resulting from picking a local file for an HTML input that looks like <input type="file" name="somefile" />.

Constructors

  • FileData
    • fdInputName :: Text

      name attribute of the corresponding HTML <input>

    • fdFileName :: Text

      name of the file on the client's disk

    • fdFileCType :: Text

      MIME type for the file

    • fdPayload :: MultipartResult tag

      path to the temporary file that has the content of the user's original file. Only valid during the execution of your handler as it gets removed right after, which means you really want to move or copy it in your handler.

Instances3Eq, Show, NFData

servant-docs

2 declarations
classclass ToMultipartSample tag a where
#

The ToMultipartSample class allows you to create sample MultipartData inputs for your type for use with Servant.Docs. This is used by the HasDocs instance for MultipartForm.

Given the example User type and FromMultipart instance from the MultipartForm documentation, here is a corresponding ToMultipartSample instance:

  data User = User { username :: Text, pic :: FilePath }

  instance ToMultipartSample Tmp User where
    toMultipartSamples proxy =
      [ ( "sample 1"
        , MultipartData
            [ Input "username" "Elvis Presley" ]
            [ FileData
                "pic"
                "playing_guitar.jpeg"
                "image/jpeg"
                "tmpservant-multipart000.buf"
            ]
        )
      ]

Methods

Orphan instances

3 instances