diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for yasi
 
+## 0.1.2.0 -- 2021-03-15
+
+ * Add `unindent` utility function.
+
 ## 0.1.1.1 -- 2021-02-14
 
  * Fix interaction of new variants and abstraction
diff --git a/src/Yasi.hs b/src/Yasi.hs
--- a/src/Yasi.hs
+++ b/src/Yasi.hs
@@ -4,8 +4,8 @@
 --  * No dependency on [haskell-src-meta](https://hackage.haskell.org/package/haskell-src-meta).
 --    It is not actively developed, has long compile times and several bugs, some of which are
 --    by design (e.g. operator fixities).
---  * Supports interpolating 'String', 'Data.Text.Text', 'Data.Text.Lazy.Text',
---    'Data.ByteString.ByteString' and 'Data.ByteString.Lazy.ByteString' (UTF8).
+--  * Supports interpolating 'String', 'Data.Text.Text', lazy 'Data.Text.Lazy.Text',
+--    'Data.ByteString.ByteString' and lazy 'Data.ByteString.Lazy.ByteString' (UTF8).
 module Yasi
   ( i,
 
diff --git a/src/Yasi/Util.hs b/src/Yasi/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Yasi/Util.hs
@@ -0,0 +1,38 @@
+-- | Utilities for working with (multi-line) literals.
+module Yasi.Util
+  ( unindent,
+  )
+where
+
+import qualified Data.Char as C
+import Data.List (dropWhileEnd)
+import qualified Data.Text as T
+
+-- $setup
+-- >>> import Yasi
+-- >>> import qualified Data.Text.IO as T
+
+-- | "Unindent" a string.
+--
+-- Also removes leading and trailing blank lines.
+--
+-- >>> :{
+-- txt = unindent [iT|
+--     foo
+--       bar
+--     baz
+-- |]
+-- :}
+--
+-- >>> T.putStr txt
+-- foo
+--   bar
+-- baz
+unindent :: T.Text -> T.Text
+unindent txt = T.unlines . map (T.drop ind) $ ls
+  where
+    isWS = T.all C.isSpace
+    ls = dropWhile isWS . dropWhileEnd isWS . T.lines $ txt
+    ind = case T.length . T.takeWhile (== ' ') <$> filter (not . isWS) ls of
+      [] -> 0
+      inds -> minimum inds
diff --git a/test/Util.hs b/test/Util.hs
new file mode 100644
--- /dev/null
+++ b/test/Util.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE CPP #-}
+
+module Util where
+
+import qualified Data.Text as T
+import Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import Test.Tasty
+import Test.Tasty.HUnit
+import Test.Tasty.Hedgehog
+import Yasi.Util
+
+#if !(MIN_VERSION_base(4,11,0))
+import Data.Semigroup ((<>))
+#endif
+
+test_unindent :: TestTree
+test_unindent =
+  testGroup
+    "unindentation"
+    [ testCase "empty string" $ do
+        unindent "" @?= "",
+      testProperty "single line" . property $ do
+        t <- forAll genTextWONL
+        unindent t === (T.dropWhile (== ' ') t <> "\n"),
+      testCase "multi line" $ do
+        unindent "\n  asdf\n\n wtf\n   foo\n" @?= " asdf\n\nwtf\n  foo\n"
+    ]
+  where
+    genTextWONL = Gen.text (Range.constant 1 50) $ Gen.filter (/= '\n') Gen.unicodeAll
diff --git a/yasi.cabal b/yasi.cabal
--- a/yasi.cabal
+++ b/yasi.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: yasi
-version: 0.1.1.1
+version: 0.1.2.0
 
 synopsis: Yet another string interpolator
 description:
@@ -38,6 +38,7 @@
   exposed-modules:
     Yasi
     Yasi.Internal
+    Yasi.Util
   build-depends:
       base >= 4.9 && < 5
     , template-haskell >= 2.11 && < 2.18
@@ -63,3 +64,4 @@
   other-modules:
       Segments
       Interpolations
+      Util
