diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## [0.0.0.0] - 2022-07-27
+
+First version
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Tom Sydney Kerckhove
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/autodocodec-servant-multipart.cabal b/autodocodec-servant-multipart.cabal
new file mode 100644
--- /dev/null
+++ b/autodocodec-servant-multipart.cabal
@@ -0,0 +1,43 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.7.
+--
+-- see: https://github.com/sol/hpack
+
+name:           autodocodec-servant-multipart
+version:        0.0.0.0
+synopsis:       Autodocodec interpreters for Servant Multipart
+homepage:       https://github.com/NorfairKing/autodocodec#readme
+bug-reports:    https://github.com/NorfairKing/autodocodec/issues
+author:         Tom Sydney Kerckhove
+maintainer:     syd@cs-syd.eu
+copyright:      2022 Tom Sydney Kerckhove
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    LICENSE
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/NorfairKing/autodocodec
+
+library
+  exposed-modules:
+      Autodocodec.Multipart
+  other-modules:
+      Paths_autodocodec_servant_multipart
+  hs-source-dirs:
+      src
+  build-depends:
+      aeson
+    , autodocodec >=0.2.0.1
+    , base >=4.7 && <5
+    , bytestring
+    , servant-multipart
+    , servant-multipart-api
+    , text
+    , unordered-containers
+    , vector
+  default-language: Haskell2010
diff --git a/src/Autodocodec/Multipart.hs b/src/Autodocodec/Multipart.hs
new file mode 100644
--- /dev/null
+++ b/src/Autodocodec/Multipart.hs
@@ -0,0 +1,252 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Autodocodec.Multipart where
+
+import Autodocodec
+import Data.Aeson as JSON
+import Data.Aeson.Types as JSON
+import qualified Data.ByteString.Lazy as LB
+import Data.Foldable
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text.Encoding as TE
+import qualified Data.Vector as V
+import Servant.Multipart as Servant
+import Servant.Multipart.API as Servant
+
+toMultipartViaCodec :: forall a tag. HasObjectCodec a => a -> MultipartData tag
+toMultipartViaCodec = toMultipartVia (objectCodec @a)
+
+toMultipartVia :: ObjectCodec a void -> a -> MultipartData tag
+toMultipartVia = flip go
+  where
+    go :: a -> ObjectCodec a void -> MultipartData tag
+    go a = \case
+      BimapCodec _ to c -> go (to a) c
+      EitherCodec _ c1 c2 -> case a of
+        Left a1 -> go a1 c1
+        Right a2 -> go a2 c2
+      DiscriminatedUnionCodec discriminator encoding _ ->
+        let (discriminatorValue, c) = encoding a
+         in mappendMultipartData
+              ( MultipartData
+                  { inputs = [Input discriminator discriminatorValue],
+                    files = []
+                  }
+              )
+              (go a c)
+      RequiredKeyCodec key vc _ ->
+        MultipartData
+          { inputs = map (Input key) (goValue a vc),
+            files = []
+          }
+      OptionalKeyCodec key vc _ ->
+        MultipartData
+          { inputs = do
+              a' <- maybeToList a
+              v <- goValue a' vc
+              pure $ Input key v,
+            files = []
+          }
+      OptionalKeyWithDefaultCodec key vc _ _ ->
+        MultipartData
+          { inputs = map (Input key) (goValue a vc),
+            files = []
+          }
+      OptionalKeyWithOmittedDefaultCodec key vc defaultValue _ ->
+        MultipartData
+          { inputs =
+              if a == defaultValue
+                then []
+                else map (Input key) (goValue a vc),
+            files = []
+          }
+      PureCodec _ -> memptyMultipartData
+      ApCodec oc1 oc2 -> mappendMultipartData (go a oc1) (go a oc2)
+
+    goValue :: a -> ValueCodec a void -> [Text]
+    goValue a = \case
+      BimapCodec _ to vc -> goValue (to a) vc
+      EitherCodec _ c1 c2 -> case a of
+        Left a1 -> goValue a1 c1
+        Right a2 -> goValue a2 c2
+      CommentCodec _ vc -> goValue a vc
+      ArrayOfCodec _ vc -> map (`goSingleValue` vc) (toList a)
+      vc -> [goSingleValue a vc]
+
+    goSingleValue :: a -> ValueCodec a void -> Text
+    goSingleValue a = \case
+      BimapCodec _ to vc -> goSingleValue (to a) vc
+      EitherCodec _ c1 c2 -> case a of
+        Left a1 -> goSingleValue a1 c1
+        Right a2 -> goSingleValue a2 c2
+      CommentCodec _ vc -> goSingleValue a vc
+      NullCodec -> "null"
+      BoolCodec _ ->
+        case a of
+          True -> "True"
+          False -> "False"
+      StringCodec _ -> a
+      vc ->
+        let value = toJSONVia vc a
+         in case value of
+              JSON.String t -> t
+              _ -> TE.decodeUtf8 (LB.toStrict (JSON.encode value))
+
+memptyMultipartData :: MultipartData tag
+memptyMultipartData =
+  MultipartData
+    { inputs = [],
+      files = []
+    }
+
+mappendMultipartData :: MultipartData tag -> MultipartData tag -> MultipartData tag
+mappendMultipartData mpd1 mpd2 =
+  MultipartData
+    { inputs = inputs mpd1 ++ inputs mpd2,
+      files = files mpd1 ++ files mpd2
+    }
+
+instance HasObjectCodec a => Servant.ToMultipart tag (Autodocodec a) where
+  toMultipart = toMultipartViaCodec . unAutodocodec
+
+fromMultipartViaCodec :: forall a tag. HasObjectCodec a => MultipartData tag -> Either String a
+fromMultipartViaCodec = fromMultipartVia (objectCodec @a)
+
+fromMultipartVia :: ObjectCodec void a -> MultipartData tag -> Either String a
+fromMultipartVia = flip go
+  where
+    go :: MultipartData tag -> ObjectCodec void a -> Either String a
+    go mpd = \case
+      BimapCodec from _ c -> go mpd c >>= from
+      EitherCodec u c1 c2 -> case u of
+        PossiblyJointUnion ->
+          case go mpd c1 of
+            Right l -> pure (Left l)
+            Left err1 -> case go mpd c2 of
+              Left err2 -> Left $ "  Previous branch failure: " <> err1 <> "\n" <> err2
+              Right r -> pure (Right r)
+        DisjointUnion ->
+          case (go mpd c1, go mpd c2) of
+            (Left _, Right r) -> pure (Right r)
+            (Right l, Left _) -> pure (Left l)
+            (Right _, Right _) -> Left "Both branches of a disjoint union succeeded."
+            (Left lErr, Left rErr) ->
+              Left $
+                unlines
+                  [ "Both branches of a disjoint union failed: ",
+                    unwords ["Left:  ", lErr],
+                    unwords ["Right: ", rErr]
+                  ]
+      DiscriminatedUnionCodec discriminator _ m -> do
+        discriminatorValue <- lookupInput discriminator mpd
+        case HashMap.lookup discriminatorValue m of
+          Nothing -> Left $ "Unexpected discriminator value: " <> show discriminatorValue
+          Just (_, c) -> go mpd c
+      RequiredKeyCodec key vc _ -> do
+        values <- lookupLInput key mpd
+        goValue values vc
+      OptionalKeyCodec key vc _ -> do
+        values <- lookupLInput key mpd
+        case values of
+          [] -> pure Nothing
+          _ -> Just <$> goValue values vc
+      OptionalKeyWithDefaultCodec key vc defaultValue _ -> do
+        values <- lookupLInput key mpd
+        case values of
+          [] -> pure defaultValue
+          _ -> goValue values vc
+      OptionalKeyWithOmittedDefaultCodec key vc defaultValue _ -> do
+        values <- lookupLInput key mpd
+        case values of
+          [] -> pure defaultValue
+          _ -> goValue values vc
+      PureCodec v -> pure v
+      ApCodec ocf oca -> go mpd ocf <*> go mpd oca
+
+    goValue :: [Text] -> ValueCodec void a -> Either String a
+    goValue ts = \case
+      BimapCodec from _ c -> goValue ts c >>= from
+      EitherCodec u c1 c2 -> case u of
+        PossiblyJointUnion ->
+          case goValue ts c1 of
+            Right l -> pure (Left l)
+            Left err1 -> case goValue ts c2 of
+              Left err2 -> Left $ "  Previous branch failure: " <> err1 <> "\n" <> err2
+              Right r -> pure (Right r)
+        DisjointUnion ->
+          case (goValue ts c1, goValue ts c2) of
+            (Left _, Right r) -> pure (Right r)
+            (Right l, Left _) -> pure (Left l)
+            (Right _, Right _) -> Left "Both branches of a disjoint union succeeded."
+            (Left lErr, Left rErr) ->
+              Left $
+                unlines
+                  [ "Both branches of a disjoint union failed: ",
+                    unwords ["Left:  ", lErr],
+                    unwords ["Right: ", rErr]
+                  ]
+      ReferenceCodec _ vc -> goValue ts vc
+      CommentCodec _ c -> goValue ts c
+      ArrayOfCodec _ vc -> V.fromList <$> mapM (`goSingleValue` vc) (toList ts)
+      vc -> case ts of
+        [t] -> goSingleValue t vc
+        _ -> Left "Expected exactly one value."
+
+    goSingleValue :: Text -> ValueCodec void a -> Either String a
+    goSingleValue t = \case
+      BimapCodec from _ c -> goSingleValue t c >>= from
+      EitherCodec u c1 c2 -> case u of
+        PossiblyJointUnion ->
+          case goSingleValue t c1 of
+            Right l -> pure (Left l)
+            Left err1 -> case goSingleValue t c2 of
+              Left err2 -> Left $ "  Previous branch failure: " <> err1 <> "\n" <> err2
+              Right r -> pure (Right r)
+        DisjointUnion ->
+          case (goSingleValue t c1, goSingleValue t c2) of
+            (Left _, Right r) -> pure (Right r)
+            (Right l, Left _) -> pure (Left l)
+            (Right _, Right _) -> Left "Both branches of a disjoint union succeeded."
+            (Left lErr, Left rErr) ->
+              Left $
+                unlines
+                  [ "Both branches of a disjoint union failed: ",
+                    unwords ["Left:  ", lErr],
+                    unwords ["Right: ", rErr]
+                  ]
+      CommentCodec _ c -> goSingleValue t c
+      ReferenceCodec _ vc -> goSingleValue t vc
+      NullCodec -> case t of
+        "null" -> Right ()
+        _ -> Left $ "not 'null': " <> show t
+      BoolCodec _ -> case t of
+        "false" -> Right False
+        "False" -> Right False
+        "true" -> Right True
+        "True" -> Right True
+        _ -> Left $ "Unknown bool: " <> show t
+      StringCodec _ -> Right t
+      vc -> case JSON.parseEither (parseJSONVia vc) (JSON.String t) of
+        Right a -> Right a
+        Left _ -> do
+          value <- JSON.eitherDecode (LB.fromStrict (TE.encodeUtf8 t))
+          JSON.parseEither (parseJSONVia vc) value
+
+lookupMInput :: Text -> MultipartData tag -> Either String (Maybe Text)
+lookupMInput iname = Right . fmap iValue . find ((== iname) . iName) . inputs
+
+lookupLInput :: Text -> MultipartData tag -> Either String [Text]
+lookupLInput iname = Right . map iValue . filter ((== iname) . iName) . inputs
+
+instance HasObjectCodec a => Servant.FromMultipart tag (Autodocodec a) where
+  fromMultipart = fmap Autodocodec . fromMultipartViaCodec
