diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for linear-free
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2025 Joosep Jääger
+
+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/linear-free.cabal b/linear-free.cabal
new file mode 100644
--- /dev/null
+++ b/linear-free.cabal
@@ -0,0 +1,46 @@
+cabal-version:      3.0
+name:               linear-free
+version:            0.1.0.0
+synopsis:           Linear free monads
+description:        
+  This package implements free monads on top of `linear-base`. Linear free monads 
+  are useful to implement domain specific languages that for example have to 
+  keep track of resources.
+license:            MIT
+license-file:       LICENSE
+author:             Joosep Jääger
+maintainer:         joosep.jaager@gmail.com
+category:           Control
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/Soupstraw/linear-free.git
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:
+      Control.Monad.Free.Linear
+    build-depends:    
+      base >=4.16 && <5,
+      linear-base ^>=0.5,
+    hs-source-dirs:   src
+    default-language: Haskell2010
+
+test-suite linear-free-test
+    import:           warnings
+    default-language: Haskell2010
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    build-depends:
+      base >=4.16 && <5,
+      containers,
+      hspec,
+      linear-base,
+      linear-free,
+      QuickCheck
diff --git a/src/Control/Monad/Free/Linear.hs b/src/Control/Monad/Free/Linear.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Free/Linear.hs
@@ -0,0 +1,89 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Control.Monad.Free.Linear (
+  MonadFreeL (..),
+  FreeL (..),
+  iter,
+  retract,
+  hoistFree,
+  foldFree,
+  unfold,
+  liftF,
+) where
+
+import qualified Control.Functor.Linear as Control
+import qualified Data.Functor.Linear as Data
+import GHC.Generics (Generic)
+import Prelude.Linear
+
+class Control.Monad m => MonadFreeL f m where
+  wrap :: f (m a) %1 -> m a
+
+data FreeL f a where
+  Pure :: a %1 -> FreeL f a
+  Free :: f (FreeL f a) %1 -> FreeL f a
+  deriving (Generic)
+
+instance Data.Functor f => Data.Functor (FreeL f) where
+  fmap f (Pure x) = Pure $ f x
+  fmap f (Free m) = Free $ Data.fmap (Data.fmap f) m
+
+instance Control.Functor f => Control.Functor (FreeL f) where
+  fmap f (Pure x) = Pure $ f x
+  fmap f (Free m) = Free $ Control.fmap (Control.fmap f) m
+
+instance Control.Functor f => Data.Applicative (FreeL f) where
+  {-# INLINE pure #-}
+  pure = Pure
+
+  Pure a <*> Pure b = Pure $ a b
+  Pure a <*> Free mb = Free $ Control.fmap a Control.<$> mb
+  Free ma <*> b = Free $ (Control.<*> b) Control.<$> ma
+
+instance Control.Functor f => Control.Applicative (FreeL f) where
+  {-# INLINE pure #-}
+  pure = Pure
+
+  Pure a <*> Pure b = Pure $ a b
+  Pure a <*> Free mb = Free $ Control.fmap a Control.<$> mb
+  Free ma <*> b = Free $ (Control.<*> b) Control.<$> ma
+
+instance Control.Functor f => Control.Monad (FreeL f) where
+  Pure x >>= f = f x
+  Free m >>= f = Free ((Control.>>= f) Control.<$> m)
+
+instance Control.Functor f => MonadFreeL f (FreeL f) where
+  {-# INLINE wrap #-}
+  wrap = Free
+
+retract :: Control.Monad f => FreeL f a %1 -> f a
+retract (Pure x) = Control.pure x
+retract (Free m) = m Control.>>= retract
+
+iter :: Control.Functor f => (f a %1 -> a) -> FreeL f a %1 -> a
+iter _ (Pure x) = x
+iter f (Free m) = f $ iter f Control.<$> m
+
+hoistFree ::
+  Control.Functor g => (forall a. f a %1 -> g a) -> FreeL f b %1 -> FreeL g b
+hoistFree _ (Pure x) = Pure x
+hoistFree f (Free m) = Free $ hoistFree f Control.<$> f m
+
+foldFree :: Control.Monad m => (forall x. f x %1 -> m x) -> FreeL f a %1 -> m a
+foldFree _ (Pure x) = Control.pure x
+foldFree f (Free m) = f m Control.>>= foldFree f
+
+unfold :: Control.Functor f => (b %1 -> Either a (f b)) -> b %1 -> FreeL f a
+unfold f b = case f b of
+  Left x -> Pure x
+  Right m -> Free $ unfold f Control.<$> m
+
+liftF :: (Control.Functor f, MonadFreeL f m) => f a %1 -> m a
+liftF = wrap . Control.fmap Control.pure
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE QualifiedDo #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Main (main) where
+
+import qualified Control.Functor.Linear as Control
+import qualified Control.Functor.Linear as Linear
+import Control.Monad.Free.Linear (FreeL (..), liftF)
+import qualified Data.Functor.Linear as Data
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+import Prelude.Linear hiding ((.))
+import qualified Prelude.Linear as L
+import Test.Hspec (describe, hspec, it, shouldBe)
+import qualified Prelude as Base
+
+data ProductType
+  = Eggs
+  | Milk
+  | Flour
+  | Dough
+  | Pancake
+  deriving (Base.Eq, Base.Ord, Show)
+
+newtype ProductId = ProductId Int
+
+data RecipeF a where
+  Buy :: ProductType -> (ProductId %1 -> a) %1 -> RecipeF a
+  Combine :: ProductType -> [ProductId] %1 -> (ProductId %1 -> a) %1 -> RecipeF a
+  Cook :: ProductType -> ProductId %1 -> (ProductId %1 -> a) %1 -> RecipeF a
+
+instance Data.Functor RecipeF where
+  fmap f (Buy t c) = Buy t $ f L.. c
+  fmap f (Combine t cs c) = Combine t cs $ f L.. c
+  fmap f (Cook t p c) = Cook t p $ f L.. c
+
+instance Control.Functor RecipeF where
+  fmap f (Buy t c) = Buy t $ f L.. c
+  fmap f (Combine t cs c) = Combine t cs $ f L.. c
+  fmap f (Cook t p c) = Cook t p $ f L.. c
+
+type Recipe a = FreeL RecipeF a
+
+buy :: ProductType -> Recipe ProductId
+buy t = liftF $ Buy t id
+
+combine :: ProductType -> [ProductId] %1 -> Recipe ProductId
+combine t ps = liftF $ Combine t ps id
+
+cook :: ProductType -> ProductId %1 -> Recipe ProductId
+cook t p = liftF $ Cook t p id
+
+pancakeRecipe :: Recipe ProductId
+pancakeRecipe = Linear.do
+  eggs <- buy Eggs
+  milk <- buy Milk
+  flour <- buy Flour
+  dough <- combine Dough [eggs, milk, flour]
+  pancake <- cook Pancake dough
+  Linear.pure pancake
+
+gatherIngredients :: Recipe a -> Map ProductType Int
+gatherIngredients (Pure _) = Map.empty
+gatherIngredients (Free x) = case x of
+  Buy t c -> Map.insertWith (Base.+) t 1 $ gatherIngredients (c productId)
+  Combine _ _ c -> gatherIngredients $ c productId
+  Cook _ _ c -> gatherIngredients $ c productId
+  where
+    -- Doesn't matter what we put here, it will not be used anyways
+    productId = ProductId 0
+
+main :: IO ()
+main = hspec $ describe "Linear free monad" $ do
+  describe "pancake recipe example" $ do
+    it "can gather the ingredients" $ do
+      gatherIngredients pancakeRecipe `shouldBe` Map.fromList
+        [ (Eggs, 1)
+        , (Milk, 1)
+        , (Flour, 1)
+        ]
