diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+## [_Unreleased_](https://github.com/freckle/iri-orphans/compare/v0.0.0.0...main)
+
+## [v0.0.0.0](https://github.com/freckle/iri-orphans/tree/v0.0.0.0)
+
+First tagged release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Freckle
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# iri-orphans
+
+Haskell package defining orphan instances for types defined in the `iri` package
diff --git a/iri-orphans.cabal b/iri-orphans.cabal
new file mode 100644
--- /dev/null
+++ b/iri-orphans.cabal
@@ -0,0 +1,135 @@
+cabal-version:      1.18
+name:               iri-orphans
+version:            0.0.0.0
+license:            MIT
+license-file:       LICENSE
+maintainer:         Freckle Education
+homepage:           https://github.com/freckle/iri-orphans#readme
+bug-reports:        https://github.com/freckle/iri-orphans/issues
+synopsis:           Orphan instances for iri
+description:        Orphan instances for types defined in the `iri` package.
+category:           Utils
+build-type:         Simple
+extra-source-files: package.yaml
+extra-doc-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+    type:     git
+    location: https://github.com/freckle/iri-orphans
+
+library
+    exposed-modules:
+        Iri.Orphans
+        Iri.Orphans.Aeson
+        Iri.Orphans.Aeson.FromJSON
+        Iri.Orphans.Aeson.ToJSON
+        Iri.Orphans.Autodocodec
+        Iri.Orphans.Autodocodec.HasCodec
+        Iri.Orphans.DebugPrint
+        Iri.Orphans.DebugPrint.ToDebugPrintValue
+        Iri.Orphans.OpenApi
+        Iri.Orphans.OpenApi.ToSchema
+        Iri.Orphans.Persistent
+        Iri.Orphans.Persistent.PersistField
+        Iri.Orphans.Persistent.PersistFieldSql
+        Iri.Orphans.QuickCheck
+        Iri.Orphans.QuickCheck.Arbitrary
+        Iri.Orphans.Serialise
+        Iri.Orphans.Serialise.Serialise
+
+    hs-source-dirs:     src
+    other-modules:      Paths_iri_orphans
+    default-language:   GHC2021
+    default-extensions:
+        ApplicativeDo BangPatterns BlockArguments DataKinds
+        DerivingStrategies DerivingVia FlexibleContexts FlexibleInstances
+        GADTs GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings
+        RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving
+        TypeApplications TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-all-missed-specialisations
+        -Wno-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-local-signatures
+        -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe
+
+    build-depends:
+        QuickCheck >=2.14.3,
+        aeson >=2.1.2.1,
+        autodocodec >=0.2.2.0,
+        base >=4.17.2.1 && <5,
+        debug-print >=0.2.0.1,
+        generic-arbitrary >=1.0.1,
+        ip >=1.7.8,
+        iri >=0.5.1.1,
+        lens >=5.2.3,
+        openapi3 >=3.2.4,
+        persistent >=2.14.6.0,
+        serialise >=0.2.6.1,
+        text >=2.0.2,
+        vector >=0.13.1.0,
+        wide-word >=0.1.6.0
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+    if impl(ghc >=9.2)
+        ghc-options: -Wno-missing-kind-signatures
+
+    if impl(ghc >=8.10)
+        ghc-options:
+            -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
+
+    if impl(ghc >=8.8)
+        ghc-options: -fwrite-ide-info
+
+test-suite spec
+    type:               exitcode-stdio-1.0
+    main-is:            Spec.hs
+    hs-source-dirs:     test
+    other-modules:
+        Iri.Orphans.QuickCheck.ArbitrarySpec
+        SpecHook
+        Paths_iri_orphans
+
+    default-language:   GHC2021
+    default-extensions:
+        ApplicativeDo BangPatterns BlockArguments DataKinds
+        DerivingStrategies DerivingVia FlexibleContexts FlexibleInstances
+        GADTs GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses
+        NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings
+        RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving
+        TypeApplications TypeFamilies
+
+    ghc-options:
+        -Weverything -Wno-all-missed-specialisations
+        -Wno-missed-specialisations -Wno-missing-exported-signatures
+        -Wno-missing-import-lists -Wno-missing-local-signatures
+        -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -threaded
+        -rtsopts -with-rtsopts=-N
+
+    build-depends:
+        QuickCheck >=2.14.3,
+        base >=4.17.2.1 && <5,
+        hspec >=2.11.10,
+        hspec-junit-formatter >=1.1.2.1,
+        iri >=0.5.1.1,
+        iri-orphans
+
+    if impl(ghc >=9.8)
+        ghc-options:
+            -Wno-missing-role-annotations -Wno-missing-poly-kind-signatures
+
+    if impl(ghc >=9.2)
+        ghc-options: -Wno-missing-kind-signatures
+
+    if impl(ghc >=8.10)
+        ghc-options:
+            -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module
+
+    if impl(ghc >=8.8)
+        ghc-options: -fwrite-ide-info
diff --git a/package.yaml b/package.yaml
new file mode 100644
--- /dev/null
+++ b/package.yaml
@@ -0,0 +1,100 @@
+name: iri-orphans
+version: 0.0.0.0
+maintainer: Freckle Education
+category: Utils
+github: freckle/iri-orphans
+synopsis: Orphan instances for iri
+description: |
+  Orphan instances for types defined in the `iri` package.
+
+extra-doc-files:
+  - README.md
+  - CHANGELOG.md
+
+extra-source-files:
+  - package.yaml
+
+ghc-options:
+  - -Weverything
+  - -Wno-all-missed-specialisations
+  - -Wno-missed-specialisations
+  - -Wno-missing-exported-signatures # re-enables missing-signatures
+  - -Wno-missing-import-lists
+  - -Wno-missing-local-signatures
+  - -Wno-monomorphism-restriction
+  - -Wno-safe
+  - -Wno-unsafe
+
+when:
+  - condition: "impl(ghc >= 9.8)"
+    ghc-options:
+      - -Wno-missing-role-annotations
+      - -Wno-missing-poly-kind-signatures
+  - condition: "impl(ghc >= 9.2)"
+    ghc-options:
+      - -Wno-missing-kind-signatures
+  - condition: "impl(ghc >= 8.10)"
+    ghc-options:
+      - -Wno-missing-safe-haskell-mode
+      - -Wno-prepositive-qualified-module
+  - condition: "impl(ghc >= 8.8)"
+    ghc-options:
+      - -fwrite-ide-info
+
+dependencies:
+  - base < 5
+
+language: GHC2021
+
+default-extensions:
+  - ApplicativeDo
+  - BangPatterns
+  - BlockArguments
+  - DataKinds
+  - DerivingStrategies
+  - DerivingVia
+  - FlexibleContexts
+  - FlexibleInstances
+  - GADTs
+  - GeneralizedNewtypeDeriving
+  - LambdaCase
+  - MultiParamTypeClasses
+  - NoImplicitPrelude
+  - NoMonomorphismRestriction
+  - OverloadedStrings
+  - RankNTypes
+  - RecordWildCards
+  - ScopedTypeVariables
+  - StandaloneDeriving
+  - TypeApplications
+  - TypeFamilies
+
+library:
+  source-dirs: src
+  dependencies:
+    - aeson
+    - autodocodec
+    - debug-print
+    - generic-arbitrary
+    - ip
+    - iri
+    - lens
+    - openapi3
+    - persistent
+    - QuickCheck
+    - serialise
+    - text
+    - vector
+    - wide-word
+
+tests:
+  spec:
+    main: Spec.hs
+    source-dirs: test
+    ghc-options: -threaded -rtsopts "-with-rtsopts=-N"
+    dependencies:
+      - hspec
+      - hspec-junit-formatter
+      - iri
+      - iri-orphans
+      - QuickCheck
diff --git a/src/Iri/Orphans.hs b/src/Iri/Orphans.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans.hs
@@ -0,0 +1,9 @@
+module Iri.Orphans () where
+
+import Iri.Orphans.Aeson ()
+import Iri.Orphans.Autodocodec ()
+import Iri.Orphans.DebugPrint ()
+import Iri.Orphans.OpenApi ()
+import Iri.Orphans.Persistent ()
+import Iri.Orphans.QuickCheck ()
+import Iri.Orphans.Serialise ()
diff --git a/src/Iri/Orphans/Aeson.hs b/src/Iri/Orphans/Aeson.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Aeson.hs
@@ -0,0 +1,4 @@
+module Iri.Orphans.Aeson () where
+
+import Iri.Orphans.Aeson.FromJSON ()
+import Iri.Orphans.Aeson.ToJSON ()
diff --git a/src/Iri/Orphans/Aeson/FromJSON.hs b/src/Iri/Orphans/Aeson/FromJSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Aeson/FromJSON.hs
@@ -0,0 +1,10 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Aeson.FromJSON () where
+
+import Autodocodec (Autodocodec (..))
+import Data.Aeson (FromJSON)
+import Iri.Data (HttpIri)
+import Iri.Orphans.Autodocodec.HasCodec ()
+
+deriving via Autodocodec HttpIri instance FromJSON HttpIri
diff --git a/src/Iri/Orphans/Aeson/ToJSON.hs b/src/Iri/Orphans/Aeson/ToJSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Aeson/ToJSON.hs
@@ -0,0 +1,10 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Aeson.ToJSON () where
+
+import Autodocodec (Autodocodec (..))
+import Data.Aeson (ToJSON)
+import Iri.Data (HttpIri)
+import Iri.Orphans.Autodocodec.HasCodec ()
+
+deriving via Autodocodec HttpIri instance ToJSON HttpIri
diff --git a/src/Iri/Orphans/Autodocodec.hs b/src/Iri/Orphans/Autodocodec.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Autodocodec.hs
@@ -0,0 +1,3 @@
+module Iri.Orphans.Autodocodec () where
+
+import Iri.Orphans.Autodocodec.HasCodec ()
diff --git a/src/Iri/Orphans/Autodocodec/HasCodec.hs b/src/Iri/Orphans/Autodocodec/HasCodec.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Autodocodec/HasCodec.hs
@@ -0,0 +1,20 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Autodocodec.HasCodec () where
+
+import Prelude
+
+import Autodocodec (HasCodec (codec), bimapCodec)
+import Data.Bifunctor (first)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Iri.Data (HttpIri)
+import Iri.Parsing.Text qualified
+import Iri.Rendering.Text qualified
+
+instance HasCodec HttpIri where
+  codec =
+    bimapCodec
+      (first T.unpack . Iri.Parsing.Text.httpIri)
+      Iri.Rendering.Text.httpIri
+      (codec @Text)
diff --git a/src/Iri/Orphans/DebugPrint.hs b/src/Iri/Orphans/DebugPrint.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/DebugPrint.hs
@@ -0,0 +1,3 @@
+module Iri.Orphans.DebugPrint () where
+
+import Iri.Orphans.DebugPrint.ToDebugPrintValue ()
diff --git a/src/Iri/Orphans/DebugPrint/ToDebugPrintValue.hs b/src/Iri/Orphans/DebugPrint/ToDebugPrintValue.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/DebugPrint/ToDebugPrintValue.hs
@@ -0,0 +1,14 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.DebugPrint.ToDebugPrintValue () where
+
+import Prelude
+
+import Data.Text (Text)
+import DebugPrint (ToDebugPrintValue (..))
+import Iri.Data (HttpIri)
+import Iri.Rendering.Text qualified
+
+instance ToDebugPrintValue HttpIri where
+  toDebugPrintValue =
+    toDebugPrintValue @Text . Iri.Rendering.Text.httpIri
diff --git a/src/Iri/Orphans/OpenApi.hs b/src/Iri/Orphans/OpenApi.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/OpenApi.hs
@@ -0,0 +1,3 @@
+module Iri.Orphans.OpenApi () where
+
+import Iri.Orphans.OpenApi.ToSchema ()
diff --git a/src/Iri/Orphans/OpenApi/ToSchema.hs b/src/Iri/Orphans/OpenApi/ToSchema.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/OpenApi/ToSchema.hs
@@ -0,0 +1,15 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.OpenApi.ToSchema () where
+
+import Prelude
+
+import Control.Lens ((&), (?~))
+import Data.OpenApi
+import Iri.Data (HttpIri)
+
+instance ToSchema HttpIri where
+  declareNamedSchema _ =
+    pure
+      $ NamedSchema (Just "HttpIri")
+      $ mempty & type_ ?~ OpenApiString
diff --git a/src/Iri/Orphans/Persistent.hs b/src/Iri/Orphans/Persistent.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Persistent.hs
@@ -0,0 +1,4 @@
+module Iri.Orphans.Persistent () where
+
+import Iri.Orphans.Persistent.PersistField ()
+import Iri.Orphans.Persistent.PersistFieldSql ()
diff --git a/src/Iri/Orphans/Persistent/PersistField.hs b/src/Iri/Orphans/Persistent/PersistField.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Persistent/PersistField.hs
@@ -0,0 +1,16 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Persistent.PersistField () where
+
+import Prelude
+
+import Control.Monad ((<=<))
+import Data.Text (Text)
+import Database.Persist
+import Iri.Data (HttpIri)
+import Iri.Parsing.Text qualified
+import Iri.Rendering.Text qualified
+
+instance PersistField HttpIri where
+  toPersistValue = toPersistValue @Text . Iri.Rendering.Text.httpIri
+  fromPersistValue = Iri.Parsing.Text.httpIri <=< fromPersistValue @Text
diff --git a/src/Iri/Orphans/Persistent/PersistFieldSql.hs b/src/Iri/Orphans/Persistent/PersistFieldSql.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Persistent/PersistFieldSql.hs
@@ -0,0 +1,14 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Persistent.PersistFieldSql () where
+
+import Prelude
+
+import Data.Proxy
+import Data.Text (Text)
+import Database.Persist.Sql
+import Iri.Data (HttpIri)
+import Iri.Orphans.Persistent.PersistField ()
+
+instance PersistFieldSql HttpIri where
+  sqlType _ = sqlType $ Proxy @Text
diff --git a/src/Iri/Orphans/QuickCheck.hs b/src/Iri/Orphans/QuickCheck.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/QuickCheck.hs
@@ -0,0 +1,3 @@
+module Iri.Orphans.QuickCheck () where
+
+import Iri.Orphans.QuickCheck.Arbitrary ()
diff --git a/src/Iri/Orphans/QuickCheck/Arbitrary.hs b/src/Iri/Orphans/QuickCheck/Arbitrary.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/QuickCheck/Arbitrary.hs
@@ -0,0 +1,142 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.QuickCheck.Arbitrary () where
+
+import Prelude
+
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Vector qualified as V
+import Iri.Data
+  ( DomainLabel (..)
+  , Fragment (..)
+  , Host (..)
+  , HttpIri
+  , Path (..)
+  , PathSegment (..)
+  , Port
+  , Query (..)
+  , RegName (..)
+  , Security (..)
+  )
+import Test.QuickCheck
+  ( Arbitrary
+  , Gen
+  , arbitrary
+  , choose
+  , elements
+  , listOf
+  , listOf1
+  , oneof
+  )
+import Test.QuickCheck.Arbitrary.Generic
+  ( GenericArbitrary
+  , genericArbitrary
+  )
+import Test.QuickCheck.Arbitrary.Generic qualified
+
+instance Arbitrary DomainLabel where
+  arbitrary = DomainLabel . T.pack <$> listOf1 char
+   where
+    char =
+      oneof
+        [ choose ('a', 'z')
+        , choose ('A', 'Z')
+        , choose ('0', '9')
+        , elements "-_~!$&'()*+,;="
+        ]
+
+instance Arbitrary Fragment where
+  arbitrary = Fragment . T.encodeUtf8 . T.pack <$> listOf char
+   where
+    char =
+      oneof
+        [ choose ('a', 'z')
+        , choose ('A', 'Z')
+        , choose ('0', '9')
+        , elements "-._~:@/?|"
+        , ucs
+        ]
+
+instance Arbitrary Host where
+  -- Don't generate an IPv6 host because
+  -- https://github.com/nikita-volkov/iri/issues/2
+  arbitrary =
+    oneof
+      [ NamedHost <$> arbitrary
+      , IpV4Host <$> genericArbitrary
+      ]
+
+deriving via
+  GenericArbitrary HttpIri
+  instance
+    Arbitrary HttpIri
+
+instance Arbitrary Path where
+  arbitrary = Path . V.fromList <$> listOf arbitrary
+
+instance Arbitrary PathSegment where
+  arbitrary = PathSegment . T.encodeUtf8 . T.pack <$> listOf1 char
+   where
+    char =
+      oneof
+        [ choose ('a', 'z')
+        , choose ('A', 'Z')
+        , choose ('0', '9')
+        , elements "-._~:@"
+        , ucs
+        ]
+
+deriving via
+  GenericArbitrary Port
+  instance
+    Arbitrary Port
+
+instance Arbitrary Query where
+  arbitrary = Query . T.encodeUtf8 . T.pack <$> listOf1 char
+   where
+    char =
+      oneof
+        [ choose ('a', 'z')
+        , choose ('A', 'Z')
+        , choose ('0', '9')
+        , elements "-._~:@/?|"
+        , ucs
+        , private
+        ]
+
+instance Arbitrary RegName where
+  arbitrary = RegName . V.fromList <$> listOf1 arbitrary
+
+deriving newtype instance
+  Arbitrary Security
+
+ucs :: Gen Char
+ucs =
+  oneof
+    [ choose ('\xA0', '\xD7FF')
+    , choose ('\xF900', '\xFDCF')
+    , choose ('\xFDF0', '\xFFEF')
+    , choose ('\x10000', '\x1FFFD')
+    , choose ('\x20000', '\x2FFFD')
+    , choose ('\x30000', '\x3FFFD')
+    , choose ('\x40000', '\x4FFFD')
+    , choose ('\x50000', '\x5FFFD')
+    , choose ('\x60000', '\x6FFFD')
+    , choose ('\x70000', '\x7FFFD')
+    , choose ('\x80000', '\x8FFFD')
+    , choose ('\x90000', '\x9FFFD')
+    , choose ('\xA0000', '\xAFFFD')
+    , choose ('\xB0000', '\xBFFFD')
+    , choose ('\xC0000', '\xCFFFD')
+    , choose ('\xD0000', '\xDFFFD')
+    , choose ('\xE1000', '\xEFFFD')
+    ]
+
+private :: Gen Char
+private =
+  oneof
+    [ choose ('\xE000', '\xF8FF')
+    , choose ('\xF0000', '\xFFFFD')
+    , choose ('\x100000', '\x10FFFD')
+    ]
diff --git a/src/Iri/Orphans/Serialise.hs b/src/Iri/Orphans/Serialise.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Serialise.hs
@@ -0,0 +1,3 @@
+module Iri.Orphans.Serialise () where
+
+import Iri.Orphans.Serialise.Serialise ()
diff --git a/src/Iri/Orphans/Serialise/Serialise.hs b/src/Iri/Orphans/Serialise/Serialise.hs
new file mode 100644
--- /dev/null
+++ b/src/Iri/Orphans/Serialise/Serialise.hs
@@ -0,0 +1,23 @@
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Iri.Orphans.Serialise.Serialise () where
+
+import Codec.Serialise (Serialise)
+import Data.WideWord.Word128 (Word128)
+import Iri.Data
+import Net.IPv4 (IPv4)
+import Net.IPv6 (IPv6)
+
+instance Serialise DomainLabel
+instance Serialise Fragment
+instance Serialise Host
+instance Serialise HttpIri
+instance Serialise IPv4
+instance Serialise IPv6
+instance Serialise Path
+instance Serialise PathSegment
+instance Serialise Port
+instance Serialise Query
+instance Serialise RegName
+instance Serialise Security
+instance Serialise Word128
diff --git a/test/Iri/Orphans/QuickCheck/ArbitrarySpec.hs b/test/Iri/Orphans/QuickCheck/ArbitrarySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Iri/Orphans/QuickCheck/ArbitrarySpec.hs
@@ -0,0 +1,30 @@
+module Iri.Orphans.QuickCheck.ArbitrarySpec
+  ( spec
+  ) where
+
+import Prelude
+
+import Data.List (intercalate)
+import Iri.Data (HttpIri (..))
+import Iri.Orphans.QuickCheck ()
+import Iri.Parsing.Text qualified
+import Iri.Rendering.Text qualified
+import Test.Hspec
+import Test.QuickCheck (counterexample, property)
+
+spec :: Spec
+spec =
+  it "generates only parseable values" $ property $ \x ->
+    counterexample
+      ( let HttpIri security host port path query fragment = x
+        in  intercalate
+              ", "
+              [ show security
+              , show host
+              , show port
+              , show path
+              , show query
+              , show fragment
+              ]
+      )
+      $ Iri.Parsing.Text.httpIri (Iri.Rendering.Text.httpIri x) == Right x
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover -Wno-missing-export-lists #-}
diff --git a/test/SpecHook.hs b/test/SpecHook.hs
new file mode 100644
--- /dev/null
+++ b/test/SpecHook.hs
@@ -0,0 +1,11 @@
+module SpecHook
+  ( hook
+  ) where
+
+import Prelude
+
+import Test.Hspec
+import Test.Hspec.JUnit.Formatter.Env as Formatter
+
+hook :: Spec -> Spec
+hook = Formatter.whenEnabled Formatter.add . parallel
