diff --git a/System/OsString.hs b/System/OsString.hs
--- a/System/OsString.hs
+++ b/System/OsString.hs
@@ -14,6 +14,7 @@
 --
 -- It captures the notion of syscall specific encoding (or the lack thereof) to avoid roundtrip issues
 -- and memory fragmentation by using unpinned byte arrays. Bytes are not touched or interpreted.
+{-# LANGUAGE CPP #-}
 module System.OsString
   (
   -- * String types
diff --git a/System/OsString/Common.hs b/System/OsString/Common.hs
--- a/System/OsString/Common.hs
+++ b/System/OsString/Common.hs
@@ -166,26 +166,41 @@
     ( SomeException, try, displayException )
 import Control.DeepSeq ( force )
 import Data.Bifunctor ( first )
-import GHC.IO
-    ( evaluate, unsafePerformIO )
+import Control.Exception ( evaluate )
+import System.IO.Unsafe ( unsafePerformIO )
 import qualified GHC.Foreign as GHC
-import Language.Haskell.TH.Quote
+#if __GLASGOW_HASKELL__ >= 914
+import Language.Haskell.TH.Lift
+    (Lift(..))
+import Language.Haskell.TH.QuasiQuoter
     ( QuasiQuoter (..) )
+#else
 import Language.Haskell.TH.Syntax
-    ( Lift (..), lift )
+    (Lift(..))
+import Language.Haskell.TH.Quote
+    ( QuasiQuoter (..) )
+#endif
 
 
 import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
 #ifdef WINDOWS
 import System.OsString.Encoding
+#if defined(__MHS__)
+import GHC.IO.Encoding( TextEncoding, utf16le )
+#else
 import System.IO
     ( TextEncoding, utf16le )
+#endif
 import GHC.IO.Encoding.UTF16 ( mkUTF16le )
 import qualified System.OsString.Data.ByteString.Short.Word16 as BSP
 #else
 import System.OsString.Encoding
+#if defined(__MHS__)
+import GHC.IO.Encoding( TextEncoding, utf8 )
+#else
 import System.IO
     ( TextEncoding, utf8 )
+#endif
 import GHC.IO.Encoding.UTF8 ( mkUTF8 )
 import qualified System.OsString.Data.ByteString.Short as BSP
 #endif
@@ -479,6 +494,7 @@
 -- | QuasiQuote a 'PosixString'. This accepts Unicode characters
 -- and encodes as UTF-8 on unix.
 #endif
+#if defined(MIN_VERSION_template_haskell) || defined(MIN_VERSION_template_haskell_quasi_quoter)
 pstr :: QuasiQuoter
 pstr =
   QuasiQuoter
@@ -507,7 +523,10 @@
       fail "illegal QuasiQuote (allowed as expression or pattern only, used as a declaration)"
   }
 #endif
-
+#else
+pstr :: a
+pstr = error "Systen.OsString.Common.pstr: no Template Haskell"
+#endif /* !defined(__MHS__) */
 
 -- | Unpack a platform string to a list of platform words.
 unpack :: PLATFORM_STRING -> [PLATFORM_WORD]
diff --git a/System/OsString/Data/ByteString/Short/Internal.hs b/System/OsString/Data/ByteString/Short/Internal.hs
--- a/System/OsString/Data/ByteString/Short/Internal.hs
+++ b/System/OsString/Data/ByteString/Short/Internal.hs
@@ -6,8 +6,10 @@
 {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE UnliftedFFITypes         #-}
 
+#if !defined(__MHS__)
 -- Required for WORDS_BIGENDIAN
 #include <ghcautoconf.h>
+#endif
 
 -- |
 -- Module      :  System.OsString.Data.ByteString.Short.Internal
@@ -44,9 +46,14 @@
 #endif
 import Foreign.Marshal.Array (withArray0, peekArray0, newArray0, withArrayLen, peekArray)
 import GHC.Exts
+#if defined(__MHS__)
+import Data.Word
+import Control.Monad.ST ( ST )
+#else
 import GHC.Word
 import GHC.ST
     ( ST (ST) )
+#endif
 import GHC.Stack ( HasCallStack )
 import Prelude hiding
     ( length )
@@ -66,6 +73,62 @@
 word16ToChar :: Word16 -> Char
 word16ToChar = C.chr . fromIntegral
 
+#if defined(__MHS__)
+import Control.Monad(forM_)
+import qualified Data.Array.Byte as B
+import Foreign.Ptr(Ptr, plusPtr)
+import Foreign.Storable (peekByteOff)
+import System.IO.Unsafe(unsafePerformIO)
+
+w8 :: Word16 -> Word8
+w8 = fromIntegral
+
+iw8 :: Int -> Word8
+iw8 = fromIntegral
+
+w16 :: Word8 -> Word16
+w16 = fromIntegral
+
+-- Use already existsing MHS types for the byte arrays
+type BA = B.ByteArray
+type MBA s = B.MutableByteArray s
+
+create :: Int -> (forall s. MBA s -> ST s ()) -> BS.ShortByteString
+create len fill =
+  runST $ do
+    mba <- B.newMutableByteArray len
+    fill mba
+    ba <- B.unsafeFreezeMutableByteArray mba
+    return $ fromBA ba
+
+asBA :: BS.ShortByteString -> BA
+asBA = B.byteStringToByteArray . BS.fromShort
+
+fromBA :: BA -> BS.ShortByteString
+fromBA = BS.toShort . B.byteArrayToByteString
+
+newPinnedByteArray :: Int -> ST s (MBA s)
+newPinnedByteArray len = B.newMutableByteArray len
+
+newByteArray :: Int -> ST s (MBA s)
+newByteArray len = B.newMutableByteArray len
+
+copyByteArray :: BA -> Int -> MBA s -> Int -> Int -> ST s ()
+copyByteArray src src_off dst dst_off len =
+  forM_ [0..len-1] $ \ i ->
+    B.writeWord8 dst (dst_off + i) (B.indexWord8 src (src_off + i))
+
+unsafeFreezeByteArray :: MBA s -> ST s BA
+unsafeFreezeByteArray = B.unsafeFreezeMutableByteArray
+
+copyAddrToByteArray :: Ptr a -> MBA RealWorld -> Int -> Int -> ST RealWorld ()
+copyAddrToByteArray src dst dst_off len =
+  forM_ [0..len-1] $ \ i -> do
+    b <- unsafeIOToST $ peekByteOff src i
+    B.writeWord8 dst (dst_off + i) b
+
+#else /* defined(__MHS__) */
+
 create :: Int -> (forall s. MBA s -> ST s ()) -> ShortByteString
 create len fill =
     runST $ do
@@ -109,9 +172,10 @@
 copyAddrToByteArray (Ptr src#) (MBA# dst#) (I# dst_off#) (I# len#) =
     ST $ \s -> case copyAddrToByteArray# src# dst# dst_off# len# s of
                  s' -> (# s', () #)
+#endif /* defined(__MHS__) */
 
 
--- this is a copy-paste from bytestring
+  -- this is a copy-paste from bytestring
 #if !MIN_VERSION_bytestring(0,10,9)
 ------------------------------------------------------------------------
 -- Primop replacements
@@ -284,11 +348,98 @@
       go mba (i - 2) ws
 
 
+#if defined(__MHS__)
 -- | Encode Word16 as little-endian.
 writeWord16Array :: MBA s
                  -> Int      -- ^ Word8 index (not Word16)
                  -> Word16
                  -> ST s ()
+writeWord16Array mba i w = do
+  let (hi, lo) = w `quotRem` 256
+  B.writeWord8 mba i     (w8 lo)
+  B.writeWord8 mba (i+1) (w8 hi)
+
+indexWord8Array :: BA
+                -> Int      -- ^ Word8 index
+                -> Word8
+indexWord8Array = B.indexWord8
+
+-- | Decode Word16 from little-endian.
+indexWord16Array :: BA
+                 -> Int      -- ^ Word8 index (not Word16)
+                 -> Word16
+indexWord16Array ba i = w16 (B.indexWord8 ba (i+1)) * 256 + w16 (B.indexWord8 ba i)
+
+setByteArray :: MBA s -> Int -> Int -> Int -> ST s ()
+setByteArray dst off len c =
+  forM_ [0..len-1] $ \ i -> B.writeWord8 dst (off + i) (iw8 c)
+
+copyMutableByteArray :: MBA s -> Int -> MBA s -> Int -> Int -> ST s ()
+copyMutableByteArray src src_off dst dst_off len =
+  forM_ [0..len-1] $ \ i -> do
+    b <- B.readWord8 src (src_off + i)
+    B.writeWord8 dst (dst_off + i) b
+
+createAndTrim :: Int -> (forall s. MBA s -> ST s (Int, a)) -> (ShortByteString, a)
+createAndTrim l fill =
+    runST $ do
+      mba <- newByteArray l
+      (l', res) <- fill mba
+      if assert (l' <= l) $ l' >= l
+          then do
+            ba <- unsafeFreezeByteArray mba
+            return (fromBA ba, res)
+          else do
+            mba2 <- newByteArray l'
+            copyMutableByteArray mba 0 mba2 0 l'
+            ba <- unsafeFreezeByteArray mba2
+            return (fromBA ba, res)
+
+createAndTrim' :: Int -> (forall s. MBA s -> ST s Int) -> ShortByteString
+createAndTrim' l fill =
+    runST $ do
+      mba <- newByteArray l
+      l' <- fill mba
+      if assert (l' <= l) $ l' >= l
+          then do
+            ba <- unsafeFreezeByteArray mba
+            return (fromBA ba)
+          else do
+            mba2 <- newByteArray l'
+            copyMutableByteArray mba 0 mba2 0 l'
+            ba <- unsafeFreezeByteArray mba2
+            return (fromBA ba)
+{-# INLINE createAndTrim' #-}
+
+createAndTrim'' :: Int -> (forall s. MBA s -> MBA s -> ST s (Int, Int)) -> (ShortByteString, ShortByteString)
+createAndTrim'' l fill =
+    runST $ do
+      mba1 <- newByteArray l
+      mba2 <- newByteArray l
+      (l1, l2) <- fill mba1 mba2
+      sbs1 <- freeze' l1 mba1
+      sbs2 <- freeze' l2 mba2
+      pure (sbs1, sbs2)
+  where
+    freeze' :: Int -> MBA s -> ST s ShortByteString
+    freeze' l' mba =
+      if assert (l' <= l) $ l' >= l
+          then do
+            ba <- unsafeFreezeByteArray mba
+            return (fromBA ba)
+          else do
+            mba2 <- newByteArray l'
+            copyMutableByteArray mba 0 mba2 0 l'
+            ba <- unsafeFreezeByteArray mba2
+            return (fromBA ba)
+
+#else /* defined(__MHS__) */
+
+-- | Encode Word16 as little-endian.
+writeWord16Array :: MBA s
+                 -> Int      -- ^ Word8 index (not Word16)
+                 -> Word16
+                 -> ST s ()
 writeWord16Array (MBA# mba#) (I# i#) (W16# w#) = ST $ \s ->
   case writeWord8ArrayAsWord16# mba# i# (word16ToLE# w#) s of
     s' -> (# s', () #)
@@ -390,6 +541,7 @@
             BA# ba# <- unsafeFreezeByteArray mba2
             return (SBS ba#)
 {-# INLINE createAndTrim'' #-}
+#endif /* defined(__MHS__) */
 
 -- Returns the index of the first match or the length of the whole
 -- bytestring if nothing matched.
@@ -419,8 +571,13 @@
 
 
 assertEven :: ShortByteString -> ShortByteString
+#if defined(__MHS__)
+assertEven sbs
+  | even (BS.length sbs) = sbs
+#else
 assertEven sbs@(SBS barr#)
   | even (I# (sizeofByteArray# barr#)) = sbs
+#endif
   | otherwise = error ("Uneven number of bytes: " <> show (BS.length sbs) <> ". This is not a Word16 bytestream.")
 
 
@@ -440,6 +597,14 @@
                      -> Int -- ^ offset for array 2
                      -> Int -- ^ length to compare
                      -> Int -- ^ like memcmp
+#if defined(__MHS__)
+compareByteArraysOff ba1 ba1off ba2 ba2off len = unsafePerformIO $
+  B.withByteArrayPtr ba1 $ \ ptr1 ->
+    B.withByteArrayPtr ba2 $ \ ptr2 ->
+      c_memcmp (plusPtr ptr1 ba1off) (plusPtr ptr2 ba2off) len
+foreign import ccall "string.h memcmp" c_memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Int
+
+#else /* defined(__MHS__) */
 #if MIN_VERSION_base(4,11,0)
 compareByteArraysOff (BA# ba1#) (I# ba1off#) (BA# ba2#) (I# ba2off#) (I# len#) =
   I# (compareByteArrays#  ba1# ba1off# ba2# ba2off# len#)
@@ -458,4 +623,5 @@
 foreign import ccall unsafe "static sbs_memcmp_off"
   c_memcmp_ByteArray :: ByteArray# -> Int -> ByteArray# -> Int -> CSize -> IO CInt
 #endif
+#endif /* defined(__MHS__) */
 
diff --git a/System/OsString/Data/ByteString/Short/Word16.hs b/System/OsString/Data/ByteString/Short/Word16.hs
--- a/System/OsString/Data/ByteString/Short/Word16.hs
+++ b/System/OsString/Data/ByteString/Short/Word16.hs
@@ -164,7 +164,9 @@
     , head
     , init
     , last
+    , length
     , map
+    , null
     , replicate
     , span
     , splitAt
@@ -173,7 +175,7 @@
     , takeWhile
     )
 import qualified Data.Foldable as Foldable
-import GHC.ST ( ST )
+import Control.Monad.ST ( ST )
 import GHC.Stack ( HasCallStack )
 import GHC.Exts ( inline )
 
@@ -379,6 +381,7 @@
     -- can't use setByteArray here, because we write UTF-16LE
     | otherwise = create (w * 2) (`go` 0)
   where
+    go :: forall s . MBA s -> Int -> ST s ()
     go mba ix
       | ix < 0 || ix >= w * 2 = pure ()
       | otherwise = writeWord16Array mba ix c >> go mba (ix + 2)
@@ -589,7 +592,7 @@
 -- > let (x, y) = span (not . isSpace) (reverse ps) in (reverse y, reverse x)
 --
 spanEnd :: (Word16 -> Bool) -> ShortByteString -> (ShortByteString, ShortByteString)
-spanEnd  p = \(assertEven -> ps) -> splitAt (findFromEndUntil (not.p) ps) ps
+spanEnd  p = \(assertEven -> ps) -> splitAt (findFromEndUntil (not . p) ps) ps
 
 -- | /O(n)/ 'splitAt' @n xs@ is equivalent to @('take' n xs, 'drop' n xs)@.
 --
diff --git a/System/OsString/Encoding/Internal.hs b/System/OsString/Encoding/Internal.hs
--- a/System/OsString/Encoding/Internal.hs
+++ b/System/OsString/Encoding/Internal.hs
@@ -2,6 +2,7 @@
            , BangPatterns
            , TypeApplications
            , MultiWayIf
+           , CPP
   #-}
 {-# OPTIONS_GHC  -funbox-strict-fields #-}
 
@@ -12,9 +13,12 @@
 import qualified System.OsString.Data.ByteString.Short.Word16 as BS16
 import System.OsString.Internal.Exception
 
+#if !defined(__MHS__)
 import GHC.Base
 import GHC.Real
 import GHC.Num
+import GHC.Show (Show (show))
+#endif
 -- import GHC.IO
 import GHC.IO.Buffer
 import GHC.IO.Encoding.Failure
@@ -23,17 +27,22 @@
 import Control.Exception (SomeException, try, Exception (displayException), evaluate)
 import qualified GHC.Foreign as GHC
 import Data.Either (Either)
-import GHC.IO (unsafePerformIO)
+import System.IO.Unsafe (unsafePerformIO)
 import Control.DeepSeq (force, NFData (rnf))
 import Data.Bifunctor (first)
 import Data.Data (Typeable)
-import GHC.Show (Show (show))
 import Numeric (showHex)
 import Foreign.C (CStringLen)
 import Data.Char (chr)
 import Foreign
 import GHC.IO.Encoding (getFileSystemEncoding, getLocaleEncoding)
 
+#if defined(__MHS__)
+import Data.Char(ord)
+unsafeChr :: Int -> Char
+unsafeChr = chr
+#endif
+
 -- -----------------------------------------------------------------------------
 -- UCS-2 LE
 --
@@ -171,7 +180,7 @@
                       c3 <- readWord8Buf iraw (ir+3)
                       let x2 = fromIntegral c3 `shiftL` 8 + fromIntegral c2
                       if | 0xd800 <= x1 && x1 <= 0xdbff
-                         , 0xdc00 <= x2 && x2 <= 0xdfff -> do
+                          ,0xdc00 <= x2 && x2 <= 0xdfff -> do
                              ow' <- writeCharBuf oraw ow (unsafeChr ((x1 - 0xd800)*0x400 + (x2 - 0xdc00) + 0x10000))
                              loop (ir+4) ow'
                          | otherwise -> do
diff --git a/System/OsString/Internal.hs b/System/OsString/Internal.hs
--- a/System/OsString/Internal.hs
+++ b/System/OsString/Internal.hs
@@ -17,12 +17,23 @@
 import Data.ByteString.Short
     ( ShortByteString )
 import Data.Char
-import Language.Haskell.TH.Quote
+#if __GLASGOW_HASKELL__ >= 914
+import Language.Haskell.TH.Lift
+    (Lift(..), lift)
+import Language.Haskell.TH.QuasiQuoter
     ( QuasiQuoter (..) )
+#else
 import Language.Haskell.TH.Syntax
-    ( Lift (..), lift )
+    (Lift(..), lift)
+import Language.Haskell.TH.Quote
+    ( QuasiQuoter (..) )
+#endif
+#if defined(__MHS__)
+import GHC.IO.Encoding ( TextEncoding )
+#else
 import System.IO
     ( TextEncoding )
+#endif
 
 import System.OsString.Encoding ( EncodingException(..) )
 import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
@@ -186,6 +197,7 @@
 -- | QuasiQuote an 'OsString'. This accepts Unicode characters
 -- and encodes as UTF-8 on unix and UTF-16 on windows.
 -- If used as pattern, requires turning on the @ViewPatterns@ extension.
+#if defined(MIN_VERSION_template_haskell) || defined(MIN_VERSION_template_haskell_quasi_quoter)
 osstr :: QuasiQuoter
 osstr =
   QuasiQuoter
@@ -214,6 +226,10 @@
       fail "illegal QuasiQuote (allowed as expression or pattern only, used as a declaration)"
   }
 #endif
+#else
+osstr :: a
+osstr = error "Systen.OsString.Internal.osstr: no Template Haskell"
+#endif /* defined(MIN_VERSION_template_haskell) || defined(MIN_VERSION_template_haskell_quasi_quoter) */
 
 
 -- | Unpack an 'OsString' to a list of 'OsChar'.
diff --git a/System/OsString/Internal/Types.hs b/System/OsString/Internal/Types.hs
--- a/System/OsString/Internal/Types.hs
+++ b/System/OsString/Internal/Types.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveLift #-}
 {-# LANGUAGE PackageImports #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TemplateHaskellQuotes #-}
@@ -36,8 +37,11 @@
 import Data.Data
 import Data.Type.Coercion (Coercion(..), coerceWith)
 import Data.Word
-import Language.Haskell.TH.Syntax
-    ( Lift (..), lift )
+#if __GLASGOW_HASKELL__ >= 914
+import Language.Haskell.TH.Lift (Lift)
+#else
+import Language.Haskell.TH.Syntax (Lift)
+#endif
 #if !MIN_VERSION_base(4,11,0)
 import Data.Semigroup
 #endif
@@ -46,7 +50,6 @@
 import System.OsString.Encoding.Internal
 import qualified System.OsString.Data.ByteString.Short as BS
 import qualified System.OsString.Data.ByteString.Short.Word16 as BS16
-import qualified Language.Haskell.TH.Syntax as TH
 
 -- Using unpinned bytearrays to avoid Heap fragmentation and
 -- which are reasonably cheap to pass to FFI calls
@@ -58,13 +61,20 @@
 
 -- | Commonly used windows string as wide character bytes.
 newtype WindowsString = WindowsString { getWindowsString :: BS.ShortByteString }
-  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData)
+  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData, Lift)
 
 -- | Decodes as UCS-2.
 instance Show WindowsString where
   -- cWcharsToChars_UCS2 is total
   show = show . cWcharsToChars_UCS2 . BS16.unpack . getWindowsString
 
+#if defined(__MHS__)
+-- No record pattern synonyms in MicroHs
+pattern WS :: BS.ShortByteString -> WindowsString
+pattern WS s = WindowsString s
+unWS :: WindowsString -> BS.ShortByteString
+unWS (WS s) = s
+#else
 -- | Just a short bidirectional synonym for 'WindowsString' constructor.
 pattern WS :: BS.ShortByteString -> WindowsString
 pattern WS { unWS } <- WindowsString unWS where
@@ -72,25 +82,23 @@
 #if __GLASGOW_HASKELL__ >= 802
 {-# COMPLETE WS #-}
 #endif
-
-
-instance Lift WindowsString where
-  lift (WindowsString bs) = TH.AppE (TH.ConE 'WindowsString) <$> (lift bs)
-#if MIN_VERSION_template_haskell(2,17,0)
-  liftTyped = TH.unsafeCodeCoerce . TH.lift
-#elif MIN_VERSION_template_haskell(2,16,0)
-  liftTyped = TH.unsafeTExpCoerce . TH.lift
-#endif
+#endif /* defined(__MHS__) */
 
 -- | Commonly used Posix string as uninterpreted @char[]@
 -- array.
 newtype PosixString = PosixString { getPosixString :: BS.ShortByteString }
-  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData)
+  deriving (Eq, Ord, Semigroup, Monoid, Typeable, Generic, NFData, Lift)
 
 -- | Prints the raw bytes without decoding.
 instance Show PosixString where
   show (PosixString ps) = show ps
 
+#if defined(__MHS__)
+pattern PS :: BS.ShortByteString -> PosixString
+pattern PS a = PosixString a
+unPS :: PosixString -> BS.ShortByteString
+unPS (PS a) = a
+#else
 -- | Just a short bidirectional synonym for 'PosixString' constructor.
 pattern PS :: BS.ShortByteString -> PosixString
 pattern PS { unPS } <- PosixString unPS where
@@ -98,15 +106,7 @@
 #if __GLASGOW_HASKELL__ >= 802
 {-# COMPLETE PS #-}
 #endif
-
-instance Lift PosixString where
-  lift (PosixString bs) = TH.AppE (TH.ConE 'PosixString) <$> (lift bs)
-#if MIN_VERSION_template_haskell(2,17,0)
-  liftTyped = TH.unsafeCodeCoerce . TH.lift
-#elif MIN_VERSION_template_haskell(2,16,0)
-  liftTyped = TH.unsafeTExpCoerce . TH.lift
-#endif
-
+#endif /* defined(__MHS__) */
 
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 type PlatformString = WindowsString
@@ -126,6 +126,17 @@
 instance Show PosixChar where
   show (PosixChar pc) = show pc
 
+#if defined(__MHS__)
+pattern WW :: Word16 -> WindowsChar
+pattern WW a = WindowsChar a
+unWW :: WindowsChar -> Word16
+unWW (WW a) = a
+
+pattern PW :: Word8 -> PosixChar
+pattern PW a = PosixChar a
+unPW :: PosixChar -> Word8
+unPW (PW a) = a
+#else
 -- | Just a short bidirectional synonym for 'WindowsChar' constructor.
 pattern WW :: Word16 -> WindowsChar
 pattern WW { unWW } <- WindowsChar unWW where
@@ -141,6 +152,7 @@
 #if __GLASGOW_HASKELL__ >= 802
 {-# COMPLETE PW #-}
 #endif
+#endif /* defined(__MHS__) */
 
 #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
 type PlatformChar = WindowsChar
@@ -159,7 +171,7 @@
 -- dealing with the internals isn't generally recommended, but supported
 -- in case you need to write platform specific code.
 newtype OsString = OsString { getOsString :: PlatformString }
-  deriving (Typeable, Generic, NFData)
+  deriving (Typeable, Generic, NFData, Lift)
 
 -- | On windows, decodes as UCS-2. On unix prints the raw bytes without decoding.
 instance Show OsString where
@@ -188,20 +200,6 @@
 instance Semigroup OsString where
     (<>) = coerce (mappend :: BS.ShortByteString -> BS.ShortByteString -> BS.ShortByteString)
 #endif
-
-
-instance Lift OsString where
-  lift xs = case coercionToPlatformTypes of
-    Left (_, co) ->
-      TH.AppE (TH.ConE 'OsString) <$> (lift $ coerceWith co xs)
-    Right (_, co) -> do
-      TH.AppE (TH.ConE 'OsString) <$> (lift $ coerceWith co xs)
-#if MIN_VERSION_template_haskell(2,17,0)
-  liftTyped = TH.unsafeCodeCoerce . TH.lift
-#elif MIN_VERSION_template_haskell(2,16,0)
-  liftTyped = TH.unsafeTExpCoerce . TH.lift
-#endif
-
 
 -- | Newtype representing a code unit.
 --
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,10 @@
 # Changelog for [`os-string` package](http://hackage.haskell.org/package/os-string)
 
+## 2.0.9 *Jan 2026*
+
+* support MicroHS wrt [#35](https://github.com/haskell/os-string/pull/35)
+* Switch from template-haskell to template-haskell-quasiquoter and -lift wrt [#36](https://github.com/haskell/os-string/pull/36)
+
 ## 2.0.8 *Aug 2025*
 
 * Add safe conversions from ShortByteString [#34](https://github.com/haskell/os-string/pull/34)
diff --git a/os-string.cabal b/os-string.cabal
--- a/os-string.cabal
+++ b/os-string.cabal
@@ -1,13 +1,13 @@
 cabal-version:      2.2
 name:               os-string
-version:            2.0.8
+version:            2.0.9
 
 -- NOTE: Don't forget to update ./changelog.md
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Julian Ospald <hasufell@posteo.de>
 maintainer:         Julian Ospald <hasufell@posteo.de>
-copyright:          Julain Ospald 2021-2023
+copyright:          Julian Ospald 2021-2023
 bug-reports:        https://github.com/haskell/os-string/issues
 homepage:
   https://github.com/haskell/os-string/blob/master/README.md
@@ -68,7 +68,15 @@
     , bytestring        >=0.11.3.0
     , deepseq
     , exceptions
-    , template-haskell
+  -- template-haskell-lift was added as a boot library in GHC-9.14
+  -- once we no longer wish to backport releases to older major releases of GHC,
+  -- this conditional can be dropped
+  if impl(ghc < 9.14)
+      build-depends: template-haskell
+  elif impl(ghc)
+      build-depends:
+        , template-haskell-lift >=0.1 && <0.2
+        , template-haskell-quasiquoter >=0.1 && <0.2
 
   ghc-options:      -Wall
 
