diff --git a/ChangeLog.md b/ChangeLog.md
index 25edeba42e696db69a5e0ddc00ea3e9cd69a7fe6..1458f662c0e9180300ca2af4f92f2f97e200bfdd 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,14 @@
 # Changelog for horizon-spec
 
+## v0.4.0
+
+* Add `HorizonExport` type for declaratively expressing
+  export settings for horizon commands.
+* Add `PackageSetExportSettings` for expressing package set
+  export settings.
+* Add `OverlayExportSettings` for expressing overlay
+  export settings.
+
 ## v0.3.0
 
 * Add `LocalSource` and `TarballSource` types
diff --git a/dhall/package.dhall b/dhall/package.dhall
index 96e3880420cf36b4a5e13b65c63b3f123622ec98..085e964e69e75fe5caf1e7a83d330f3a3fd25c22 100644
--- a/dhall/package.dhall
+++ b/dhall/package.dhall
@@ -3,6 +3,10 @@ let Prelude =
       ? https://raw.githubusercontent.com/dhall-lang/dhall-lang/v21.1.0/Prelude/package.dhall
           sha256:0fed19a88330e9a8a3fbe1e8442aa11d12e38da51eb12ba8bcb56f3c25d0854a
 
+let FilePath = Text
+
+let Directory = Text
+
 let Name = Text
 
 let Version = Text
@@ -53,8 +57,22 @@ let PackageSet = { compiler : Compiler, packages : PackageList }
 
 let Overlay = PackageList
 
+let PackageSetExportSettings =
+      { packagesDir : Directory
+      , packageSetFile : FilePath
+      , packageSet : PackageSet
+      }
+
+let OverlayExportSettings =
+      { packagesDir : Directory, overlayFile : FilePath, overlay : Overlay }
+
+let HorizonExport =
+      < MakePackageSet : PackageSetExportSettings
+      | MakeOverlay : OverlayExportSettings
+      >
+
 let callHackage
-    : Name → Version → Attr HaskellPackage.Type
+    : Name → Version → PackageEntry
     = λ(name : Name) →
       λ(version : Version) →
         { mapKey = name
@@ -64,7 +82,7 @@ let callHackage
         }
 
 let callGit
-    : Name → Url → Revision → Optional Subdir → Attr HaskellPackage.Type
+    : Name → Url → Revision → Optional Subdir → PackageEntry
     = λ(name : Name) →
       λ(url : Url) →
       λ(revision : Revision) →
@@ -76,7 +94,7 @@ let callGit
         }
 
 let callLocal
-    : Name → Subdir → Attr HaskellPackage.Type
+    : Name → Subdir → PackageEntry
     = λ(name : Name) →
       λ(subdir : Subdir) →
         { mapKey = name
@@ -84,7 +102,7 @@ let callLocal
         }
 
 let callTarball
-    : Name → Url → Attr HaskellPackage.Type
+    : Name → Url → PackageEntry
     = λ(name : Name) →
       λ(url : Url) →
         { mapKey = name
@@ -111,13 +129,19 @@ let modPackageSet
 in  { Attr
     , CabalFlag
     , Compiler
+    , Directory
+    , FilePath
     , Flag
     , HaskellSource
     , HaskellPackage
+    , HorizonExport
     , Modifiers
     , Name
+    , OverlayExportSettings
     , Overlay
+    , PackageEntry
     , PackageList
+    , PackageSetExportSettings
     , PackageSet
     , Revision
     , Subdir
diff --git a/horizon-spec.cabal b/horizon-spec.cabal
index 2def5f5c565c7354e0945e1a0cc24b0041ee0b5a..89c783f48c917a39cd2f2029714b3b49074da82c 100644
--- a/horizon-spec.cabal
+++ b/horizon-spec.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               horizon-spec
-version:            0.3.0
+version:            0.4.0
 synopsis:           Horizon Stable Package Set Type Definitions
 description:
   This package contains the type definitions for the Horizon stable package set (https://horizon-haskell.net). This is a schema used to define package sets sourcing from hackage and git.
diff --git a/src/Horizon/Spec.hs b/src/Horizon/Spec.hs
index fe47d641cc0c6a93616f9e93f586af9a56897df7..d07b3d0ccaa1e0f0f84ff67acad14186990ad72a 100644
--- a/src/Horizon/Spec.hs
+++ b/src/Horizon/Spec.hs
@@ -1,24 +1,30 @@
-{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveAnyClass        #-}
+{-# LANGUAGE DuplicateRecordFields #-}
 module Horizon.Spec
-  ( Url(MkUrl, fromUrl)
-  , Repo(MkRepo, fromRepo)
-  , Subdir(MkSubdir, fromSubdir)
-  , Name(MkName, fromName)
-  , Version (MkVersion, fromVersion)
+  ( CabalFlag(MkCabalFlag)
+  , Compiler(MkCompiler, fromCompiler)
+  , Flag(Enable, Disable)
   , GitSource(MkGitSource, url, revision, subdir)
   , HackageSource(MkHackageSource, name, version)
-  , LocalSource(MkLocalSource, fromLocalSource)
-  , TarballSource(MkTarballSource, fromTarballSource)
+  , HaskellPackage(MkHaskellPackage, source, modifiers, flags)
   , HaskellSource(FromGit, FromHackage, FromTarball, FromLocal)
-  , Flag(Enable, Disable)
-  , CabalFlag(MkCabalFlag)
+  , HorizonExport(MakePackageSet, MakeOverlay)
+  , LocalSource(MkLocalSource, fromLocalSource)
   , Modifiers(doJailbreak, doCheck, enableProfiling)
-  , HaskellPackage(MkHaskellPackage, source, modifiers, flags)
-  , Revision(MkRevision, fromRevision)
-  , Compiler(MkCompiler, fromCompiler)
-  , PackageList(MkPackageList, fromPackageList)
+  , Name(MkName, fromName)
+  , OverlayExportSettings(MkOverlayExportSettings, packagesDir, overlayFile, overlay)
+  , OverlayFile(MkOverlayFile, fromOverlayFile)
   , Overlay(MkOverlay, fromOverlay)
+  , PackageList(MkPackageList, fromPackageList)
+  , PackageSetExportSettings(MkPackageSetExportSettings, packagesDir, packageSetFile, packageSet)
+  , PackageSetFile(MkPackageSetFile, fromPackageSetFile)
   , PackageSet(MkPackageSet, compiler, packages)
+  , Repo(MkRepo, fromRepo)
+  , Revision(MkRevision, fromRevision)
+  , Subdir(MkSubdir, fromSubdir)
+  , TarballSource(MkTarballSource, fromTarballSource)
+  , Url(MkUrl, fromUrl)
+  , Version (MkVersion, fromVersion)
   )
   where
 
@@ -26,7 +32,7 @@ import           Data.Kind  (Type)
 import           Data.Map   (Map)
 import           Data.Text  (Text)
 import           Dhall      (FromDhall, Generic, ToDhall)
-import           Path       (Dir, Path, Rel)
+import           Path       (Dir, File, Path, Rel)
 import           Path.Dhall ()
 
 
@@ -147,3 +153,44 @@ data PackageSet where
                   , packages :: PackageList } -> PackageSet
   deriving stock (Show, Eq, Generic)
   deriving anyclass (FromDhall, ToDhall)
+
+type PackagesDir :: Type
+newtype PackagesDir where
+  MkPackagesDir :: { fromPackagesDir :: Path Rel Dir } -> PackagesDir
+  deriving stock (Show, Eq, Generic)
+  deriving newtype (FromDhall, ToDhall)
+
+type PackageSetFile :: Type
+newtype PackageSetFile where
+  MkPackageSetFile :: { fromPackageSetFile :: Path Rel File } -> PackageSetFile
+  deriving stock (Show, Eq, Generic)
+  deriving newtype (FromDhall, ToDhall)
+
+type OverlayFile :: Type
+newtype OverlayFile where
+  MkOverlayFile :: { fromOverlayFile :: Path Rel File } -> OverlayFile
+  deriving stock (Show, Eq, Generic)
+  deriving newtype (FromDhall, ToDhall)
+
+type PackageSetExportSettings :: Type
+data PackageSetExportSettings where
+  MkPackageSetExportSettings :: { packagesDir :: PackagesDir
+                                , packageSetFile :: PackageSetFile
+                                , packageSet :: PackageSet } -> PackageSetExportSettings
+  deriving stock (Show, Eq, Generic)
+  deriving anyclass (FromDhall, ToDhall)
+
+type OverlayExportSettings :: Type
+data OverlayExportSettings where
+  MkOverlayExportSettings :: { packagesDir :: PackagesDir
+                             , overlayFile :: OverlayFile
+                             , overlay :: Overlay } -> OverlayExportSettings
+  deriving stock (Show, Eq, Generic)
+  deriving anyclass (FromDhall, ToDhall)
+
+type HorizonExport :: Type
+data HorizonExport where
+  MakePackageSet :: PackageSetExportSettings -> HorizonExport
+  MakeOverlay :: OverlayExportSettings -> HorizonExport
+  deriving stock (Show, Eq, Generic)
+  deriving anyclass (FromDhall, ToDhall)
diff --git a/test/Spec.hs b/test/Spec.hs
index 3a17a877910e4151bdfe6144b32bbfeabd5aa144..e63def4d2f693e64b2db288e4822aab5ec4c4822 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -8,7 +8,7 @@ import           Data.Text.Encoding        (encodeUtf8)
 import           Dhall                     (FromDhall, ToDhall, auto, embed,
                                             inject, inputFile)
 import           Dhall.Pretty              (layout, prettyExpr)
-import           Horizon.Spec              (Overlay, PackageSet)
+import           Horizon.Spec              (HorizonExport, Overlay, PackageSet)
 import           Prettyprinter.Render.Text (renderStrict)
 import           Test.Syd                  (Spec, describe,
                                             doNotRandomiseExecutionOrder, it,
@@ -35,3 +35,5 @@ main = sydTest $ doNotRandomiseExecutionOrder $ sequential $ do
   expectedOutputTest (Proxy @PackageSet) "modified-package-set"
   expectedOutputTest (Proxy @Overlay) "sample-overlay"
   expectedOutputTest (Proxy @Overlay) "modified-overlay"
+  expectedOutputTest (Proxy @HorizonExport) "sample-package-set-export"
+  expectedOutputTest (Proxy @HorizonExport) "sample-overlay-export"
diff --git a/test/data/sample-overlay-export/input.dhall b/test/data/sample-overlay-export/input.dhall
new file mode 100644
index 0000000000000000000000000000000000000000..d8fc0d258b48b74fc47a7d5586a411c17a78d0bc
--- /dev/null
+++ b/test/data/sample-overlay-export/input.dhall
@@ -0,0 +1,18 @@
+let H = ../../../dhall/package.dhall
+
+in  H.HorizonExport.MakeOverlay
+      { overlayFile = "overlay.nix"
+      , packagesDir = "pkgs"
+      , overlay =
+        [ H.callHackage "lens" "5.2"
+        , H.callGit
+            "Cabal-syntax"
+            "https://gitlab.haskell.org/ghc/packages/Cabal"
+            "e714824c6e652bf894f914bc57feccc15759668a"
+            (Some "Cabal-syntax")
+        , H.callTarball
+            "network-mux"
+            "https://input-output-hk.github.io/cardano-haskell-packages/package/network-mux-0.2.0.0.tar.gz"
+        , H.callLocal "myPackage" "./myPackage"
+        ]
+      }
diff --git a/test/data/sample-overlay-export/output.golden b/test/data/sample-overlay-export/output.golden
new file mode 100644
index 0000000000000000000000000000000000000000..03d0c7adf1dbc3bded323a7066fb072deb0b9535
--- /dev/null
+++ b/test/data/sample-overlay-export/output.golden
@@ -0,0 +1,114 @@
+< MakePackageSet :
+    { packagesDir : Text
+    , packageSetFile : Text
+    , packageSet :
+        { compiler : Text
+        , packages :
+            List
+              { mapKey : Text
+              , mapValue :
+                  { source :
+                      < FromGit :
+                          { url : Text
+                          , revision : Text
+                          , subdir : Optional Text
+                          }
+                      | FromHackage : { name : Text, version : Text }
+                      | FromLocal : Text
+                      | FromTarball : Text
+                      >
+                  , modifiers :
+                      { doJailbreak : Bool
+                      , doCheck : Bool
+                      , enableProfiling : Bool
+                      }
+                  , flags : List < Enable : Text | Disable : Text >
+                  }
+              }
+        }
+    }
+| MakeOverlay :
+    { packagesDir : Text
+    , overlayFile : Text
+    , overlay :
+        List
+          { mapKey : Text
+          , mapValue :
+              { source :
+                  < FromGit :
+                      { url : Text, revision : Text, subdir : Optional Text }
+                  | FromHackage : { name : Text, version : Text }
+                  | FromLocal : Text
+                  | FromTarball : Text
+                  >
+              , modifiers :
+                  { doJailbreak : Bool, doCheck : Bool, enableProfiling : Bool }
+              , flags : List < Enable : Text | Disable : Text >
+              }
+          }
+    }
+>.MakeOverlay
+  { packagesDir = "pkgs/"
+  , overlayFile = "overlay.nix"
+  , overlay =
+    [ { mapKey = "Cabal-syntax"
+      , mapValue =
+        { source =
+            < FromGit : { url : Text, revision : Text, subdir : Optional Text }
+            | FromHackage : { name : Text, version : Text }
+            | FromLocal : Text
+            | FromTarball : Text
+            >.FromGit
+              { url = "https://gitlab.haskell.org/ghc/packages/Cabal"
+              , revision = "e714824c6e652bf894f914bc57feccc15759668a"
+              , subdir = Some "Cabal-syntax/"
+              }
+        , modifiers =
+          { doJailbreak = True, doCheck = False, enableProfiling = True }
+        , flags = [] : List < Enable : Text | Disable : Text >
+        }
+      }
+    , { mapKey = "lens"
+      , mapValue =
+        { source =
+            < FromGit : { url : Text, revision : Text, subdir : Optional Text }
+            | FromHackage : { name : Text, version : Text }
+            | FromLocal : Text
+            | FromTarball : Text
+            >.FromHackage
+              { name = "lens", version = "5.2" }
+        , modifiers =
+          { doJailbreak = True, doCheck = False, enableProfiling = True }
+        , flags = [] : List < Enable : Text | Disable : Text >
+        }
+      }
+    , { mapKey = "myPackage"
+      , mapValue =
+        { source =
+            < FromGit : { url : Text, revision : Text, subdir : Optional Text }
+            | FromHackage : { name : Text, version : Text }
+            | FromLocal : Text
+            | FromTarball : Text
+            >.FromLocal
+              "myPackage/"
+        , modifiers =
+          { doJailbreak = True, doCheck = False, enableProfiling = True }
+        , flags = [] : List < Enable : Text | Disable : Text >
+        }
+      }
+    , { mapKey = "network-mux"
+      , mapValue =
+        { source =
+            < FromGit : { url : Text, revision : Text, subdir : Optional Text }
+            | FromHackage : { name : Text, version : Text }
+            | FromLocal : Text
+            | FromTarball : Text
+            >.FromTarball
+              "https://input-output-hk.github.io/cardano-haskell-packages/package/network-mux-0.2.0.0.tar.gz"
+        , modifiers =
+          { doJailbreak = True, doCheck = False, enableProfiling = True }
+        , flags = [] : List < Enable : Text | Disable : Text >
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/test/data/sample-package-set-export/input.dhall b/test/data/sample-package-set-export/input.dhall
new file mode 100644
index 0000000000000000000000000000000000000000..0d21aae795c8071fb8c43edce74b2ff82cdf104c
--- /dev/null
+++ b/test/data/sample-package-set-export/input.dhall
@@ -0,0 +1,21 @@
+let H = ../../../dhall/package.dhall
+
+in  H.HorizonExport.MakePackageSet
+      { packageSetFile = "initial-packages.nix"
+      , packagesDir = "pkgs"
+      , packageSet =
+        { compiler = "ghc-9.4.2"
+        , packages =
+          [ H.callHackage "lens" "5.2"
+          , H.callGit
+              "Cabal-syntax"
+              "https://gitlab.haskell.org/ghc/packages/Cabal"
+              "e714824c6e652bf894f914bc57feccc15759668a"
+              (Some "Cabal-syntax")
+          , H.callTarball
+              "network-mux"
+              "https://input-output-hk.github.io/cardano-haskell-packages/package/network-mux-0.2.0.0.tar.gz"
+          , H.callLocal "myPackage" "./myPackage"
+          ]
+        }
+      }
diff --git a/test/data/sample-package-set-export/output.golden b/test/data/sample-package-set-export/output.golden
new file mode 100644
index 0000000000000000000000000000000000000000..08667b27f19bbfe39b6d85fce887e8727af6f337
--- /dev/null
+++ b/test/data/sample-package-set-export/output.golden
@@ -0,0 +1,121 @@
+< MakePackageSet :
+    { packagesDir : Text
+    , packageSetFile : Text
+    , packageSet :
+        { compiler : Text
+        , packages :
+            List
+              { mapKey : Text
+              , mapValue :
+                  { source :
+                      < FromGit :
+                          { url : Text
+                          , revision : Text
+                          , subdir : Optional Text
+                          }
+                      | FromHackage : { name : Text, version : Text }
+                      | FromLocal : Text
+                      | FromTarball : Text
+                      >
+                  , modifiers :
+                      { doJailbreak : Bool
+                      , doCheck : Bool
+                      , enableProfiling : Bool
+                      }
+                  , flags : List < Enable : Text | Disable : Text >
+                  }
+              }
+        }
+    }
+| MakeOverlay :
+    { packagesDir : Text
+    , overlayFile : Text
+    , overlay :
+        List
+          { mapKey : Text
+          , mapValue :
+              { source :
+                  < FromGit :
+                      { url : Text, revision : Text, subdir : Optional Text }
+                  | FromHackage : { name : Text, version : Text }
+                  | FromLocal : Text
+                  | FromTarball : Text
+                  >
+              , modifiers :
+                  { doJailbreak : Bool, doCheck : Bool, enableProfiling : Bool }
+              , flags : List < Enable : Text | Disable : Text >
+              }
+          }
+    }
+>.MakePackageSet
+  { packagesDir = "pkgs/"
+  , packageSetFile = "initial-packages.nix"
+  , packageSet =
+    { compiler = "ghc-9.4.2"
+    , packages =
+      [ { mapKey = "Cabal-syntax"
+        , mapValue =
+          { source =
+              < FromGit :
+                  { url : Text, revision : Text, subdir : Optional Text }
+              | FromHackage : { name : Text, version : Text }
+              | FromLocal : Text
+              | FromTarball : Text
+              >.FromGit
+                { url = "https://gitlab.haskell.org/ghc/packages/Cabal"
+                , revision = "e714824c6e652bf894f914bc57feccc15759668a"
+                , subdir = Some "Cabal-syntax/"
+                }
+          , modifiers =
+            { doJailbreak = True, doCheck = False, enableProfiling = True }
+          , flags = [] : List < Enable : Text | Disable : Text >
+          }
+        }
+      , { mapKey = "lens"
+        , mapValue =
+          { source =
+              < FromGit :
+                  { url : Text, revision : Text, subdir : Optional Text }
+              | FromHackage : { name : Text, version : Text }
+              | FromLocal : Text
+              | FromTarball : Text
+              >.FromHackage
+                { name = "lens", version = "5.2" }
+          , modifiers =
+            { doJailbreak = True, doCheck = False, enableProfiling = True }
+          , flags = [] : List < Enable : Text | Disable : Text >
+          }
+        }
+      , { mapKey = "myPackage"
+        , mapValue =
+          { source =
+              < FromGit :
+                  { url : Text, revision : Text, subdir : Optional Text }
+              | FromHackage : { name : Text, version : Text }
+              | FromLocal : Text
+              | FromTarball : Text
+              >.FromLocal
+                "myPackage/"
+          , modifiers =
+            { doJailbreak = True, doCheck = False, enableProfiling = True }
+          , flags = [] : List < Enable : Text | Disable : Text >
+          }
+        }
+      , { mapKey = "network-mux"
+        , mapValue =
+          { source =
+              < FromGit :
+                  { url : Text, revision : Text, subdir : Optional Text }
+              | FromHackage : { name : Text, version : Text }
+              | FromLocal : Text
+              | FromTarball : Text
+              >.FromTarball
+                "https://input-output-hk.github.io/cardano-haskell-packages/package/network-mux-0.2.0.0.tar.gz"
+          , modifiers =
+            { doJailbreak = True, doCheck = False, enableProfiling = True }
+          , flags = [] : List < Enable : Text | Disable : Text >
+          }
+        }
+      ]
+    }
+  }
\ No newline at end of file